CloudFront-Forwarded-Protoヘッダを使ってCloudFrontへの通信プロトコルをオリジン側で確認してみた

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

はじめに

清水です。前回のエントリに続いて、今回もCloudFront-ELB-EC2な構成でのネタです。ユーザからの通信がHTTPSまたはHTTPだとして、かつCloudFront-ELB間はHTTPS通信と設定した環境とします。オリジンであるEC2側ではユーザからCloudFrontへの通信プロトコルをどのように判別すればよいか調査してみました。

ELB-EC2のような構成でEC2側でユーザからELBへの通信プロトコルを確認する場合はX-Forwarded-Protoヘッダを使用します。

ただ今回のようなCloudFront-ELB-EC2の構成で、さらにはCloudFrontーELB間についてはHTTPS通信となるよう設定した場合については、X-Forwarded-Protoヘッダはユーザの通信プロトコルにかかわらずHTTPSになってしまいます。

ユーザからCloudFrontへの通信プロトコルを確認するには、CloudFrontにより追加されるCloudFront-Forwarded-Protoヘッダを確認する必要があります。またCloudFrontでヘッダを転送しないよう設定している場合はこのCloudFront-Forwarded-Protoヘッダもオリジンに転送されませんので、CloudFront-Forwarded-Protoヘッダだけでも転送するよう設定する必要があります。

本エントリではこのCloudFront-Forwarded-Protoヘッダのオリジンへの転送方法と、実際にオリジンであるEC2上でどのように確認できるのかをまとめてみます。なお検証に用いたEC2側ではApache+PHPが稼働し、以下のコードでEC2側でリクエストに含まれるヘッダをすべて出力するようにしています。

<?php

foreach (getallheaders() as $name => $value) {
    echo "$name: $value\n";
}

?>

CloudFront-Forwarded-Protoヘッダをオリジンに転送しない場合

まずはCloudFrontの設定でヘッダを転送しない設定での挙動を確認してみます。CloudFront側ではCache Based on Selected Request HeadersNone(Improves Caching)と設定してある状態ですね。

まずはHTTPSでのアクセスです。上半分はcurlコマンドで出力しているヘッダ情報、1行挟んで下半分がオリジンで出力しているヘッダ情報です。オリジン側ではX-Forwarded-Proto: httpsまでは確認できますね。

$ curl -i https://www.example.com/
HTTP/2 200
content-type: text/html; charset=UTF-8
content-length: 360
date: Tue, 31 Jul 2018 12:12:37 GMT
server: Apache/2.4.27 (Amazon) PHP/5.6.35
x-powered-by: PHP/5.6.35
x-cache: Miss from cloudfront
via: 1.1 ab7b99dd2576a3bf9957a9db22bcccdd.cloudfront.net (CloudFront)
x-amz-cf-id: w0PQfLbYhJuDyVxiN9QOYOh9DVPOuyJhFTqI84XhoVBJy5cQ79TtCA==

X-Forwarded-For: 1.XX.XX.XX, 13.113.203.179
X-Forwarded-Proto: https
X-Forwarded-Port: 443
Host: elb.example.com
X-Amzn-Trace-Id: Root=1-5b605235-e68654a09897695ee2937990
X-Amz-Cf-Id: RqK1mWNDkHje_j0o9nQvZ2L80wa1YF9B0f40-WOfN_zr0pA_GzCwug==
User-Agent: Amazon CloudFront
Via: 2.0 ab7b99dd2576a3bf9957a9db22bcccdd.cloudfront.net (CloudFront)

続いてHTTPでのアクセスです。なおキャッシュ期間を調整してキャッシュがなくなるよう設定してあります。X-Forwarded-Protoをみるとhttpsとなっていますね。これはELBからのHTTPS通信を行っているためです。

$ curl -i http://www.example.com/
HTTP/1.1 200 OK
Content-Type: text/html; charset=UTF-8
Content-Length: 359
Connection: keep-alive
Date: Tue, 31 Jul 2018 12:13:49 GMT
Server: Apache/2.4.27 (Amazon) PHP/5.6.35
X-Powered-By: PHP/5.6.35
X-Cache: Miss from cloudfront
Via: 1.1 fbc942e4bd97accc585402ff91f07cbb.cloudfront.net (CloudFront)
X-Amz-Cf-Id: aKLHcNpD_zpf8BJRNZCZW1arw0b3ob_uz9Vg7pkCQ-k8Zto6XRFDUQ==

X-Forwarded-For: 1.XX.XX.XX, 13.113.203.53
X-Forwarded-Proto: https
X-Forwarded-Port: 443
Host: elb.example.com
X-Amzn-Trace-Id: Root=1-5b60527d-97a73e6073816cbc856f9d18
X-Amz-Cf-Id: B0husBFBqpX73xfPYDOEz92BrZQ1A-LjS9BujTlj-Orl5AApxVJQxQ==
User-Agent: Amazon CloudFront
Via: 1.1 fbc942e4bd97accc585402ff91f07cbb.cloudfront.net (CloudFront)

ということで、CloudFront-Forwarded-Protoヘッダもありませんし、ユーザからCloudFrontへの通信プロトコルの判断をオリジンEC2で行うことは難しそうです。

CloudFront-Forwarded-Protoヘッダをオリジンに転送するよう設定する

CloudFrontへの通信プロトコルを確認するために、CloudFront-Forwarded-Protoヘッダを転送するようディストリビューションを設定します。Cache Based on Selected Request Headersの項目でWhitelistを選びCloudFront-Forwarded-Protoを選択します。

要件等によってすべてのヘッダを転送するように設定しても、CloudFront-Forwarded-Protoも転送することになりますので、同様の効果が得られます。

CloudFront-Forwarded-Protoヘッダをオリジンに転送した場合

ディストリビューションへの設定反映が完了したら実際にアクセスしてみて挙動を確認します。まずはCloudFront-Forwarded-Protoヘッダのみを転送するよう設定し、HTTPSでアクセスしたパターンです。CloudFront-Forwarded-Proto: httpsが確認できます。

 $ curl -i https://www.example.com/
HTTP/2 200
content-type: text/html; charset=UTF-8
content-length: 394
date: Tue, 31 Jul 2018 12:56:22 GMT
server: Apache/2.4.27 (Amazon) PHP/5.6.35
x-powered-by: PHP/5.6.35
x-cache: Miss from cloudfront
via: 1.1 2c6248542582010bc022ce33969f1509.cloudfront.net (CloudFront)
x-amz-cf-id: Zgz5u72QEN0IrHFsOoxr1NtvRUHDtdIHoiFU5SbIdeg9mH9wIvo2Ew==

X-Forwarded-For: 1.XX.XX.XX, 13.113.203.179
X-Forwarded-Proto: https
X-Forwarded-Port: 443
Host: elb.example.com
X-Amzn-Trace-Id: Root=1-5b605c76-7a88d87a99d630f6476324ec
X-Amz-Cf-Id: v8l_t1gTnymMOfqfKqeAzMWp1Du9Jhr6u9X1NWWbGexNdygzEQ783g==
CloudFront-Forwarded-Proto: https
User-Agent: Amazon CloudFront
Via: 2.0 2c6248542582010bc022ce33969f1509.cloudfront.net (CloudFront)

続いて同じ条件でHTTPでアクセスしてみます。オリジンのEC2側でCloudFront-Forwarded-Proto: httpとなっていることが確認できますね。

 $ curl -i http://www.example.com/
HTTP/1.1 200 OK
Content-Type: text/html; charset=UTF-8
Content-Length: 393
Connection: keep-alive
Date: Tue, 31 Jul 2018 12:56:29 GMT
Server: Apache/2.4.27 (Amazon) PHP/5.6.35
X-Powered-By: PHP/5.6.35
X-Cache: Miss from cloudfront
Via: 1.1 00e04c56f991d537f76f3de8fe1a96c6.cloudfront.net (CloudFront)
X-Amz-Cf-Id: QngdQjOG5Nmm3i1Pksr-nueL8b8UPTOCX_KqM3B7nxpRXxKfzAjavQ==

X-Forwarded-For: 1.XX.XX.XX, 13.113.203.141
X-Forwarded-Proto: https
X-Forwarded-Port: 443
Host: elb.example.com
X-Amzn-Trace-Id: Root=1-5b605c7d-4407ad64d4136a087e34b128
X-Amz-Cf-Id: d670MYBiRVc8RKGzRHxE1PV8TaPBfyx6viMpsZ7lD55zuoriD3GEYQ==
CloudFront-Forwarded-Proto: http
User-Agent: Amazon CloudFront
Via: 1.1 00e04c56f991d537f76f3de8fe1a96c6.cloudfront.net (CloudFront)

ついでにすべてのヘッダを転送するよう設定した場合についても挙動を確認してみます。HTTPSの場合ですがCloudFront-Forwarded-Proto: httpsが含まれていることがわかります。

 $ curl -i https://www.example.com/
HTTP/2 200
content-type: text/html; charset=UTF-8
content-length: 552
date: Tue, 31 Jul 2018 12:57:19 GMT
server: Apache/2.4.27 (Amazon) PHP/5.6.35
x-powered-by: PHP/5.6.35
x-cache: Miss from cloudfront
via: 1.1 4a0e5a774647d45df2730ca2d10b4249.cloudfront.net (CloudFront)
x-amz-cf-id: _rMfKWZR4juuRtip9gmVYFa0vhOhb9lallBS7WCibj_Myzr13HvZeA==

X-Forwarded-For: 1.XX.XX.XX, 54.239.196.100
X-Forwarded-Proto: https
X-Forwarded-Port: 443
Host: www.example.com
X-Amzn-Trace-Id: Root=1-5b605caf-94db6f50192f3d1d39aff06f
X-Amz-Cf-Id: PhGdLwE-wEpvG_7YllwcdIFBFd5XE2-s8RB5Nd7KIL_NuvcKZ1iuTw==
User-Agent: curl/7.54.0
Via: 2.0 4a0e5a774647d45df2730ca2d10b4249.cloudfront.net (CloudFront)
CloudFront-Is-Mobile-Viewer: false
CloudFront-Is-Tablet-Viewer: false
CloudFront-Is-SmartTV-Viewer: false
CloudFront-Is-Desktop-Viewer: true
CloudFront-Viewer-Country: JP
Accept: */*
CloudFront-Forwarded-Proto: https

続いてすべてのヘッダヲ転送するようにした、HTTPの場合です。こちらも意図通りCloudFront-Forwarded-Proto: httpが確認できますね。

 $ curl -i http://www.example.com/
HTTP/1.1 200 OK
Content-Type: text/html; charset=UTF-8
Content-Length: 550
Connection: keep-alive
Date: Tue, 31 Jul 2018 12:57:40 GMT
Server: Apache/2.4.27 (Amazon) PHP/5.6.35
X-Powered-By: PHP/5.6.35
X-Cache: Miss from cloudfront
Via: 1.1 7b63dc372a4330b28d4dd1f11ec139a7.cloudfront.net (CloudFront)
X-Amz-Cf-Id: j1Qd_EperFor-kAN3jBbUrIj8DGuoB_f_d7VlJt_ie--_vGEU1CEHA==

X-Forwarded-For: 1.XX.XX.XX, 54.239.196.74
X-Forwarded-Proto: https
X-Forwarded-Port: 443
Host: www.example.com
X-Amzn-Trace-Id: Root=1-5b605cc4-4be8ec14d6f1a1a5a6108649
X-Amz-Cf-Id: ZOSaW4-P_OTVu2zRD60bf1Bh3hRw7kpfVy6Vw45MG5b9Sq648yBnoQ==
User-Agent: curl/7.54.0
Via: 1.1 7b63dc372a4330b28d4dd1f11ec139a7.cloudfront.net (CloudFront)
CloudFront-Is-Mobile-Viewer: false
CloudFront-Is-Tablet-Viewer: false
CloudFront-Is-SmartTV-Viewer: false
CloudFront-Is-Desktop-Viewer: true
CloudFront-Viewer-Country: JP
Accept: */*
CloudFront-Forwarded-Proto: http

まとめ

CloudFrontでCloudFront-Forwarded-Protoヘッダをオリジンに転送するよう設定し、オリジン側でリクエストに含まれるヘッダ情報を確認することで、クライアントがCloudFrontにどの通信プロトコルで接続しているのか判別できることを確認しました。

今回この「CloudFrontにどの通信プロトコルで接続しているかオリジン側で確認する」ことに至ったきっかけは、別エントリにまとめていますApacheのディレクトリ名補完時のリダイレクト処理に関する問題だったりします。このCloudFront-Forwarded-Protoヘッダをオリジン側で確認することで、クライアントからの通信がHTTPSかHTTPかを判断し、HTTPSならApache側で別のLocationにリダイレクトする際にもHTTPSにする、など設定できればディレクト名補完時のリダイレクトなどの処理もHTTPSで完結するようになるかと思います。