CloudFront-ELB-EC2構成でApacheのディレクトリ名補完リダイレクト時の問題をAWS側で可能な限り対応してみた

CloudFront-ELB-EC2な構成でEC2上のApacheがディレクトリ名のスラッシュを補完するためのリダイレクトレスポンスを返す際に、リダイレクト先ドメインが変わってしまう、リダイレクト先のプロトコルが変わってしまう、という問題に遭遇しました。CloudFrontのHostヘッダ転送、HTTPSリダイレクト機能を用いてAWS側のみで対応する方法を検討してみました。
2018.07.31

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

清水です。先日以下のようなCloudFront-ELB-EC2な構成で、EC2上のApacheがディレクトリ名のスラッシュを補完するためのリダイレクトレスポンスを返したときに発生する問題と出くわしました。解決策をできる限りAWS側で対応する方法を検討してみたのでまとめてみます。

Apacheのディレクトリ名のスラッシュの補完によるリダイレクトは、例えば www.example.com/directoryにアクセスした際、Apacheで自動的にwww.example.com/directory/(最後に/がついています)にリダイレクトする機能のこと、として本エントリでは扱います。(これ自体はApacheの機能でデフォルト状態で有効になっているかと思います。)

このApacheのディレクトリ名補完リダイレクトが発生した際に、今回のCloudFront-ELB-EC2構成で発生した問題点は以下2点です。

  1. リダイレクト先のURLがCloudFrontのドメイン(www.example.com)ではなくELBのドメイン(elb.example.com)になってしまう
  2. HTTPSでアクセスしていてもリダイレクトしたタイミングでHTTPになってしまう

これらに対して、それぞれ以下で対応を試みました。

  1. CloudFrontのヘッダ転送項目にHostヘッダを追加
  2. CloudFrontでHTTPをHTTPSにリダイレクト

以下、詳細についてまとめます。

リダイレクト先のURLがCloudFrontではなくELBのドメイン名になってしまうことの対応

CloudFront-ELB-EC2な構成を構築した際、ふと、http://www.example.com/directoryにアクセスしました。ここでdirectoryはディレクトリを指しますが、末尾の/が抜けている状態です。この場合Apacheにより自動的にディレクトリ名末尾の/が保管され、http://www.example.com/directory/にアクセスできる、と思ったのですが、実際はELBのドメイン名であるhttp://elb.example.com/directory/へのリダイレクトとなってしまいました。

curlコマンドで確認すると以下の具合です。

$ curl -i http://www.example.com/directory
HTTP/1.1 301 Moved Permanently
Content-Type: text/html; charset=iso-8859-1
Content-Length: 271
Connection: keep-alive
Date: Mon, 30 Jul 2018 16:30:09 GMT
Server: Apache/2.4.33 (Amazon)
Location: http://elb.example.com/directory/
X-Cache: Miss from cloudfront
Via: 1.1 19e783c3014d0c6d956b507eb51109e9.cloudfront.net (CloudFront)
X-Amz-Cf-Id: e56eahjFwI6deolUg1QXGK87VwC18l6qKL8v1Y3sOSuWCWDqBzRfsw==

<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>301 Moved Permanently</title>
</head><body>
<h1>Moved Permanently</h1>
<p>The document has moved <a href="http://elb.example.com/directory/">here</a>.</p>
</body></html>

同様のケースの解決策をWeb検索して探してみたところ、下記のページで紹介されている方法がありました。

こちらのページでの解決策ではApacheのmod_rewriteを利用して対応をしています。ですがなるべくAWS側の設定のみで(Apache側の設定を変えずに)対応できないか考えました。

リダイレクトの際にApacheから返される301のレスポンスにはELBのドメイン名が記されていますが、この情報がどこから来ているのか?というのポイントになりそうです。また今回の問題が発生したパスについてはCloudFrontのBehavior設定にて、いずれのヘッダもオリジンに転送しないように設定していたのですが、他のすべてのヘッダをオリジンに転送するようBehaviorで設定していたパスについてはこの問題が発生しませんでした。このことから、Hostヘッダの情報をApache側で利用して301のレスポンスのLocationを設定しているのでは?と予想ができました。で、あればCloudFrontでHostヘッダをオリジンに転送するよう設定すれば、Apacheから返される301のレスポンスにELBではなくCloudFrontのドメインが記されるはずです。ということで下記ページを参考にHostヘッダを転送するよう、設定してみました。

結果としてはこの予想があたり、CloudFrontでHostヘッダをオリジンに転送することでApacheからの301のレスポンスにCloudFrontのドメイン名が記され、想定しているCloudFrontのドメインにリダイレクトされるようになりました。CloudFrontにHostヘッダをオリジンに転送するよう設定した場合の挙動は以下のようになりました。

$ curl -i http://www.example.com/directory
HTTP/1.1 301 Moved Permanently
Content-Type: text/html; charset=iso-8859-1
Content-Length: 252
Connection: keep-alive
Date: Mon, 30 Jul 2018 16:31:48 GMT
Server: Apache/2.4.33 (Amazon)
Location: http://www.example.com/directory/
X-Cache: Miss from cloudfront
Via: 1.1 93f6d59fc01947819e9685ed8fc9464f.cloudfront.net (CloudFront)
X-Amz-Cf-Id: liIIeS_XhpcwNYMQBkMNwYa0PYguOaMRUoHDdO8QnvQNCeUOEpqJLg==

<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>301 Moved Permanently</title>
</head><body>
<h1>Moved Permanently</h1>
<p>The document has moved <a href="http://www.example.com/directory/">here</a>.</p>
</body></html>

以上のようにCloudFrontでHostヘッダをオリジンに転送するよう設定したことで、確かにApache側で特段設定せずAWS(CloudFront)側の設定のみで対策が可能になりました。しかし、Apache含めEC2のアプリケーション上で他にHostヘッダを使用していないか?その挙動が変わることはないか?の確認は必要となるかと思います。

HTTPSでアクセスしていてもリダイレクトしたタイミングでHTTPになってしまうことの対応

CloudFrontからオリジンにHostヘッダを転送するよう設定したことで、意図したとおりのCloudFrontのドメインにリダイレクトするようになりました。ただしもう1点、問題が発生しました。HTTPSでアクセスしていても、ApacheからリダイレクトされるとHTTPでのアクセスとなってしまします。実際に挙動を確認してみると、以下のようにApacheからの301のレスポンスで確かにHTTPが指定されています。

$ curl -i https://www.example.com/directory
HTTP/2 301
content-type: text/html; charset=iso-8859-1
content-length: 251
location: http://www.example.com/directory/
date: Mon, 30 Jul 2018 16:33:15 GMT
server: Apache/2.4.33 (Amazon)
x-cache: Miss from cloudfront
via: 1.1 ffcc29f4ddfff427c3ebd2667dbc5202.cloudfront.net (CloudFront)
x-amz-cf-id: AYLzUbvQ5fbJQc7O0M_0NXVK34-X1Yi7Kl9fjWRHSrUmMJpjAMm7eQ==

<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>301 Moved Permanently</title>
</head><body>
<h1>Moved Permanently</h1>
<p>The document has moved <a href="http://www.example.com/directory/">here</a>.</p>
</body></html>

構成をもう一度見直してみると、EC2にはHTTPでアクセスするようにしています。そのためApache側で接続しているプロトコルをそのままリダイレクトのレスポンスのLocation情報に使用しているのではと考えられます。ということはEC2側への接続をHTTPSとすることでリダイレクト先もHTTPSとなる可能性もありますが、なるべくEC2上ではSSLの終端処理をさせたくない、ということもあり、やはりAWS側でなんとかする方法を模索してみました。

今回はCloudFrontを使用していることもあり、CloudFrontの設定でHTTPでのアクセス際はHTTPSにリダイレクトするよう設定してみました。設定は先ほどのヘッダ転送と同じく、各Behaviorごとに設定できます。Viewer Protocol Policyで「Redirect HTTP to HTTPS」を選択しましょう。(参考: ビューワーと CloudFront との通信で HTTPS を必須にする - Amazon CloudFront

これにより以下の挙動になり、一度HTTPを挟みますが、結果としてはHTTPSのページへとリダイレクトされるようになりました。

  1. https://www.example.com/directory にアクセス
  2. Apacheが http://www.example.com/directory/ への301リダイレクトレスポンスを返す
  3. http://www.example.com/directory/ にアクセス
  4. CloudFrontにて https://www.example.com/directory/ への301リダイレクトレスポンスが返される
  5. https://www.example.com/directory/ にアクセス

こちらもcurlでの挙動は下記のようになります。

$ curl -i https://www.example.com/directory
HTTP/2 301
content-type: text/html; charset=iso-8859-1
content-length: 255
location: http://www.example.com/directory/
date: Mon, 30 Jul 2018 16:34:50 GMT
server: Apache/2.4.33 (Amazon)
x-cache: Miss from cloudfront
via: 1.1 93f6d59fc01947819e9685ed8fc9464f.cloudfront.net (CloudFront)
x-amz-cf-id: sQPlRbxdYFWCYnSEVXueKrW3Zr_4HFu8tV-Q9_3c0Ky7C51z-9GxCw==

<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>301 Moved Permanently</title>
</head><body>
<h1>Moved Permanently</h1>
<p>The document has moved <a href="http://www.example.com/directory/">here</a>.</p>
</body></html>

$ curl -i http://www.example.com/directory/
HTTP/1.1 301 Moved Permanently
Server: CloudFront
Date: Mon, 30 Jul 2018 16:35:11 GMT
Content-Type: text/html
Content-Length: 183
Connection: keep-alive
Location: https://www.example.com/directory/
X-Cache: Redirect from cloudfront
Via: 1.1 b291b21c612e764f4bf23bc28c9e37f5.cloudfront.net (CloudFront)
X-Amz-Cf-Id: ySI-Q-4SRUvEuJmTNQmy7ZWKsJylReEa5-stcqbck9KcaCbkfCj1bg==

<html>
<head><title>301 Moved Permanently</title></head>
<body bgcolor="white">
<center><h1>301 Moved Permanently</h1></center>
<hr><center>CloudFront</center>
</body>
</html>

$ curl -i https://www.example.com/directory/
HTTP/2 200
content-type: text/html; charset=UTF-8
content-length: 21
date: Mon, 30 Jul 2018 16:35:21 GMT
server: Apache/2.4.33 (Amazon)
last-modified: Mon, 30 Jul 2018 16:34:33 GMT
etag: "15-5723a095576ec"
accept-ranges: bytes
x-cache: Miss from cloudfront
via: 1.1 d4f6df874b3bd54e4b45bc75dab191a1.cloudfront.net (CloudFront)
x-amz-cf-id: EvU_3OZ_iyn9DBM9qN41pm0Iahjymj21id3gBHKLc_1jF2o-hr88aA==

directory/index.html

なお、いったんHTTPを挟んでしまうことをどうしても避けたい場合は、やはりApache側でエンドクライアントの通信プロトコルを判断し、リダイレクトのレスポンスの段階でHTTPSを指定するしかないかなと考えています。

まとめ

CloudFront-ELB-EC2な構成でEC2上のApacheがディレクトリ名のスラッシュを補完するためのリダイレクトレスポンスを返す際の、リダイレクト先ドメインが変わってしまう、リダイレクト先のプロトコルが変わってしまう、という問題に対して、CloudFrontのHostヘッダのオリジンへの転送、HTTPのHTTPSへのリダイレクト機能を用いて、AWS側のみで対応する方法を検討してみました。

個人的にこれまでCloudFrontを利用する際は、オリジンがS3だったり単純な(ディレクトリ補完を意識しない)ホスティングだったり、またすべてのヘッダを転送するようなキャッシュをしない設定だったりしたことが多かかったからか、今回まとめた内容については意識したことがありませんでした。オリジンにApacheなどWebホスティングする場合はいろいろ考慮すべき点がありますね、注意していきたいと思います。