CloudFrontへの”cloudfront.net”ドメインでのアクセスをCloudFront Functionsでブロックしてみた

独自ドメインを設定したCloudFront Distributionへ、わざわざCloudFrontのデフォルトのドメイン名でアクセスはさせたくない、という場合の対策の一例です。CloudFront Functionsを使ってブロックしてみました。
2023.07.16

はじめに

清水です。AWSのCDNサービスであるAmazon CloudFrontでは自動的に割り当てられるドメイン、もしくは設定した独自のドメインでDistributionにアクセスすることができます。独自ドメイン(例えばwww.example.com)を使用してユーザにアクセスさせる場合には、この「自動的に割り当てられるドメイン」が不要になることもあります。

自動的に割り当てられるドメインはd111111abcdef8.cloudfront.netといった形式のcloudfront.netのサブドメインに該当するもので、プレフィックスとなる先頭のd以外はランダムな英数字です。当てずっぽうでアクセスされることはあまりないかと思いますが、完全にないとは言い切れません。また独自ドメインをCNAMEレコードで登録している場合は、名前解決の過程でこのcloudfront.netのサブドメインが判明してしまいます。cloudfront.netのサブドメインによる意図しないアクセスが発生しうるため、ユーザのCloudFront Distributionへのアクセスは設定した独自ドメインのみ許可して、自動的に割り当てられるデフォルトのドメインからのアクセスはブロックしたい、というケースもあるかと思います。

この自動的に割り当てられるcloudfront.netのサブドメインでのアクセスをブロックする方法としては、いくつかの方法が考えられるかと思います。今回はCloudFront Functionsを用いて実現してみました。

CloudFront Functionsで"cloudfront.net"ドメインでのアクセスをブロックしてみた

CloudFront Distributionの準備

まずは動作検証に使用するCloudFront Distributionを作成していきます。オリジンにはApache HTTP Serverの稼働するEC2インスタンスを準備しました。ルートオブジェクトindex.htmlにアクセスすると以下のようにシンプルなテキストを返します。

% curl http://ec2-18-XXX-XXX-172.ap-northeast-1.compute.amazonaws.com
<html>
  <head><title>block-default-domain-access</title></head>
v  <body>block-default-domain-access.example.net</body>
</html>

このEC2のPublic IPv4 DNSをOrigin domainとしてDistributionを作成します。この段階ではまだCloudFront Functionsは関連付けません。(のちほど設定します。)

独自ドメインまわりは以下のように設定しました。Alternate domain name (CNAME)にユーザがアクセスする想定のドメインとなるblock-default-domain-access.example.netを設定します。

Distributionが作成できました。

あわせて、block-default-domain-access.example.netのDNS側のレコード設定も実施しておきます。今回はRoute 53上でA ALIASレコードを登録しました。

"cloudfront.net"ドメインをブロックしていないときの状況を確認

さて、CloudFrontのデフォルトドメインd111111abcdef8.cloudfront.netでのアクセスをブロックしていない状況での動作を確認しておきましょう。

独自に設定したドメインでのアクセスはもちろん可能です。今回、CloudFront DistributionのBehavior設定でHTTP、HTTPSの双方のアクセスに応ずるよう設定してあるので、どちらでもアクセスすることができます。

独自ドメインへのHTTPSアクセス

% curl https://block-default-domain-access.example.net
<html>
  <head><title>block-default-domain-access</title></head>
  <body>block-default-domain-access.example.net</body>
</html>

独自ドメインへのHTTPアクセス

% curl http://block-default-domain-access.example.net
<html>
  <head><title>block-default-domain-access</title></head>
  <body>block-default-domain-access.example.net</body>
</html>

続いてCloudFrontのデフォルトドメインd111111abcdef8.cloudfront.netでのアクセスについてです。こちらもHTTPS、HTTPの双方でアクセスすることができます。

(余談ですが、筆者はこの動作確認をするまで、てっきりHTTPSのアクセスは証明書関連でエラーが出現するかと思っていました。独自ドメイン用の証明書を設定してもcloudfront.net用の証明書も有効、どちらのHTTPSアクセスも可能なんですね。ただし、CloudFrontとオリジンとの通信がHTTPSの場合で、オリジンの証明書の状況などによっては、CloudFrontからオリジンへの接続不可といったエラーが出る可能性があります。)

デフォルトドメインへのHTTPSアクセス

% curl https://d34loxxxxxxxxx.cloudfront.net
<html>
  <head><title>block-default-domain-access</title></head>
  <body>block-default-domain-access.example.net</body>
</html>

デフォルトドメインへのHTTPアクセス

% curl http://d34loxxxxxxxxx.cloudfront.net
<html>
  <head><title>block-default-domain-access</title></head>
  <body>block-default-domain-access.example.net</body>
</html>

"cloudfront.net"ドメインでのアクセスをブロックするCloudFront Functionsの設定

続いて本題となる、CloudFront Distributionのデフォルトのcloudfront.netドメインでアクセスしたらブロックする処理をCloudFront Functionsで設定します。

CloudFrontマネジメントコンソールのFunctionsから[Create function]ボタンで進み、NameとDescriptionを入力してFunctionを作成します。

続く画面、BuildタブのFunction codeのDevelopmentを書き換えます。

CloudFront Functionsのコードは以下となります。(なお筆者はコーディングに明るくなく、見様見真似で作成したコードとなります。必ずしも適切なコードではなく、あくまで動作検証を最優先としている点にご注意ください。)Hostヘッダにcloudfront.netが含まれていたら403 Forbiddenを返す処理としています。

function handler(event) {
    var request = event.request;
    var host = request.headers.host.value;

    if (host.includes('cloudfront.net')) {
        return {
            statusCode: 403,
            statusDescription: 'Forbidden',
            body: {
                "encoding": "text",
                "data": "<html><head><title>403 Forbidden</title></head><body><center><h1>403 Forbidden</h1></center></body></html>"
            }
        };
    }

    return request;
}

[Save changes]ボタンでコードを保存します。続いてTestタブに移りTest functionでコードをテストします。Event typeはViewer requestを選択、Request headersでまずは独自ドメインblock-default-domain-access.example.netでのアクセス想定のテストです。403 Forbiddenにならずにリクエストをそのまま返していますね。

続いて今回のポイントとなるcloudfront.netドメインでアクセスした想定のテストです。こちらは想定通り403 Forbiddenが返りました。

なお、今回のコードではcloudfront.net.example.netというような「cloudfront.netが含まれる独自ドメイン」はカバーできていません。CloudFrontのデフォルトドメインではありませんが403 Forbiddenを返す挙動となります。厳密には意図と異なる動作となりますが、今回は目をつぶっておきます。

またHostヘッダが存在しない場合、FailedとなりCloudFront Functionsの実行に失敗します。基本的にCloudFrontへのリクエストはHostヘッダが必須である認識ですので(CloudFrontで独自ドメイン利用の際にディストリビューション側にもCNAME設定が必要な点をその動作とともに検証してみた | DevelopersIO)、今回はこちらのケースも黙認します。(あくまで動作検証が目的ということで。)

ということで、このコードで動作確認OKとして、[Publish function]します。

Associated distributionsの項目が現れるので、[Add association]ボタンでFunctionをDistributionに関連付けます。

該当Distribution IDを選択、Event typeはViewer requestを選択します。今回はDefault (*) のCache behaviorに関連付けました。

Functionの関連付けが完了しました。

"cloudfront.net"ドメインでのアクセスをブロックするCloudFront Functionsの動作確認

それではcloudfront.netドメインをブロックした環境にアクセスしてみましょう。自動で割り当てられるcloudfornt.netのサブドメインでアクセスすると、HTTPS/HTTPとも以下のように403 Forbiddenが返りました。

デフォルトドメインへのHTTPSアクセス(デフォルトドメインへのブロック機能あり)

% curl -i https://d34loxxxxxxxxx.cloudfront.net
HTTP/2 403
server: CloudFront
date: Sat, 15 Jul 2023 11:33:44 GMT
content-length: 106
x-cache: FunctionGeneratedResponse from cloudfront
via: 1.1 18fbxxxxxxxxxxxxxxxxxxxxxxxx6f10.cloudfront.net (CloudFront)
x-amz-cf-pop: NRT57-P3
x-amz-cf-id: 3pe9xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxWw==

<html><head><title>403 Forbidden</title></head><body><center><h1>403 Forbidden</h1></center></body></html>

デフォルトドメインへのHTTPアクセス(デフォルトドメインへのブロック機能あり)

% curl -i http://d34loxxxxxxxxx.cloudfront.net
HTTP/1.1 403 Forbidden
Server: CloudFront
Date: Sat, 15 Jul 2023 11:34:09 GMT
Content-Length: 106
Connection: keep-alive
X-Cache: FunctionGeneratedResponse from cloudfront
Via: 1.1 026dxxxxxxxxxxxxxxxxxxxxxxxx80c6.cloudfront.net (CloudFront)
X-Amz-Cf-Pop: NRT57-P3
X-Amz-Cf-Id: 5gCzxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx4w==

<html><head><title>403 Forbidden</title></head><body><center><h1>403 Forbidden</h1></center></body></html>

続いて独自ドメインでのアクセスです。こちらは以下のように、通常のアクセスができますね。

独自ドメインへのHTTPSアクセス(デフォルトドメインへのブロック機能あり)

% curl -i https://block-default-domain-access.example.net
HTTP/2 200
content-type: text/html; charset=UTF-8
content-length: 128
date: Sat, 15 Jul 2023 08:00:41 GMT
server: Apache/2.4.27 (Amazon) PHP/5.6.35
last-modified: Sat, 15 Jul 2023 07:59:25 GMT
etag: "80-60xxxxxxxxbb3"
accept-ranges: bytes
x-cache: Hit from cloudfront
via: 1.1 4004xxxxxxxxxxxxxxxxxxxxxxxx96f4.cloudfront.net (CloudFront)
x-amz-cf-pop: NRT57-C4
x-amz-cf-id: KIOexxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxIA==
age: 12824

<html>
  <head><title>block-default-domain-access</title></head>
  <body>block-default-domain-access.example.net</body>
</html>

独自ドメインへのHTTPアクセス(デフォルトドメインへのブロック機能あり)

% curl -i http://block-default-domain-access.example.net
HTTP/1.1 200 OK
Content-Type: text/html; charset=UTF-8
Content-Length: 128
Connection: keep-alive
Date: Sat, 15 Jul 2023 08:00:41 GMT
Server: Apache/2.4.27 (Amazon) PHP/5.6.35
Last-Modified: Sat, 15 Jul 2023 07:59:25 GMT
ETag: "80-60xxxxxxxxbb3"
Accept-Ranges: bytes
X-Cache: Hit from cloudfront
Via: 1.1 8ea6xxxxxxxxxxxxxxxxxxxxxxxx1612.cloudfront.net (CloudFront)
X-Amz-Cf-Pop: NRT57-C4
X-Amz-Cf-Id: P4w2xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-Q==
Age: 12839

<html>
  <head><title>block-default-domain-access</title></head>
  <body>block-default-domain-access.example.net</body>
</html>

"cloudfront.net"ドメインでアクセスした場合に独自ドメインにリダイレクトさせるパターン

CloudFront Functionsのコードを以下のように変更すれば、CloudFrontのデフォルトドメインにアクセスした場合に本来の独自ドメインにリダイレクトさせる、ということも可能です。(なお以下のコードについては、もともとの(デフォルトドメインへの)リダイレクトに対するドメイン以外、例えばリクエストのパスやクエリ文字列などはリダイレクト先独自ドメインに引き継ぐようにはなっていないのでご注意ください。)

function handler(event) {
    var request = event.request;
    var host = request.headers.host.value;
    var newurl = 'https://block-default-domain-access.example.net/';

    if (host.includes('cloudfront.net')) {
        return {
            statusCode: 301,
            statusDescription: 'Moved Permanently',
             headers:
            { "location": { "value": newurl } }
        };
    }

    return request;
}

デフォルトのcloudfornt.netのサブドメインでアクセスしてみます。301でリダイレクトされていますね。

デフォルトドメインへのHTTPSアクセス(301リダイレクト版)

% curl -i https://d34loxxxxxxxxx.cloudfront.net
HTTP/2 301
server: CloudFront
date: Sat, 15 Jul 2023 13:51:08 GMT
content-length: 0
location: https://block-default-domain-access.example.net/
x-cache: FunctionGeneratedResponse from cloudfront
via: 1.1 3a5axxxxxxxxxxxxxxxxxxxxxxxx3916.cloudfront.net (CloudFront)
x-amz-cf-pop: NRT57-P3
x-amz-cf-id: unwnxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxNg==

独自ドメインをCNAMEレコードで登録してた場合のデフォルトドメインの判明について

CloudFront Functionsを用いたcloudfront.netサブドメインへのアクセスのブロックについて確認してきました。ここで、冒頭で述べた「独自ドメインをCNAMEレコードで登録している場合は、名前解決の過程でcloudfront.netのサブドメインが判明してしまう」、という事象について改めて確認してみます。

まず今回の動作検証のように、独自ドメインをA ALIASレコードで登録した場合です。CloudFront + Route 53の構成ではこちらが推奨されます。メリットしかありません。(Amazon Route 53のALIASレコード利用のススメ | DevelopersIO

digコマンドで独自ドメインの名前解決をしてみると、直接IPアドレスが返ってきます。A ALIASレコードを利用する限りはcloudfront.netサブドメインが判明することはなさそうです。

 % dig block-default-domain-access.example.net

; <<>> DiG 9.10.6 <<>> block-default-domain-access.example.net
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 52665
;; flags: qr rd ra; QUERY: 1, ANSWER: 4, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1220
;; QUESTION SECTION:
;block-default-domain-access.example.net. IN A

;; ANSWER SECTION:
block-default-domain-access.example.net. 60 IN A 18.65.206.15
block-default-domain-access.example.net. 60 IN A 18.65.206.33
block-default-domain-access.example.net. 60 IN A 18.65.206.101
block-default-domain-access.example.net. 60 IN A 18.65.206.93

;; Query time: 36 msec
;; SERVER: 192.168.11.1#53(192.168.11.1)
;; WHEN: Sat Jul 15 22:53:15 JST 2023
;; MSG SIZE  rcvd: 132

対して、独自ドメインをCNAMEレコードで登録してみた場合です。CloudFront + Route 53の構成でも以下のようにCNAMEレコードでの登録自体は可能です。

独自ドメインのCNAMEレコードをdigコマンドで名前解決した結果が下記になります。最終的にIPアドレスが返ってくるのですが、いちどCloudFrontのデフォルトのドメイン名に名前解決してから、そのcloudfront.netサブドメインを名前解決するかたちでIPアドレスを得ています。独自ドメインをCNAMEレコードで登録している場合は名前解決の際にcloudfront.netのサブドメインが判明してしまうことが確認できましたね。

 % dig block-default-domain-access.example.net

; <<>> DiG 9.10.6 <<>> block-default-domain-access.example.net
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 5093
;; flags: qr rd ra; QUERY: 1, ANSWER: 5, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1220
;; QUESTION SECTION:
;block-default-domain-access.example.net. IN A

;; ANSWER SECTION:
block-default-domain-access.example.net. 60 IN CNAME d34loxxxxxxxxx.cloudfront.net.
d34loxxxxxxxxx.cloudfront.net. 60 IN    A       18.65.206.101
d34loxxxxxxxxx.cloudfront.net. 60 IN    A       18.65.206.15
d34loxxxxxxxxx.cloudfront.net. 60 IN    A       18.65.206.33
d34loxxxxxxxxx.cloudfront.net. 60 IN    A       18.65.206.93

;; Query time: 35 msec
;; SERVER: 192.168.11.1#53(192.168.11.1)
;; WHEN: Sun Jul 16 01:37:57 JST 2023
;; MSG SIZE  rcvd: 172

ほかの実現方法との簡単な比較

さて、冒頭で「cloudfront.netサブドメインでのアクセスをブロックする方法は複数ある」ということを述べました。ほかに考えられる実現方法とその簡単な比較を行ってみましょう。

1つ目はAWS WAFを用いた実現方法が検討できるかと思います。今回のCloudFront Functionsのコードで実現したような、「hostヘッダにcloudfront.netという文字列が含まれていたらブロックする」というRuleを作成、このRuleを使用するWeb ACLをCloudFront Distributionに関連付けるかたちです。AWS WAFでのHostヘッダの扱いについては以下ブログエントリなどをご確認ください。

AWS WAFを用いた方法と今回のCloudFront Functionsで実現する方法、まずはコスト面を比較してみます。AWS WAFの場合は月々の固定費として1つのWeb ACLあたり$5、1つのRuleあたり$1の費用がかかります。さらにリクエスト料金として100万リクエストあたり$0.6が発生します。対して、CloudFront Functionsは固定費はなく、100万リクエストあたり$0.1の費用のみ発生です。固定費が発生せず、リクエスト単価も安いことからコスト面だけみればCloudFront Functionsがよさそうです。

  • AWS WAF
    • Web ACL $5.00/month
    • Rule $1.00/month
    • リクエスト料金 $0.60/100万リクエスト
  • CloudFront Functions
    • リクエスト料金 $0.10/100万リクエスト

ただし、AWS WAFでほかのRuleも適用する(適用している)といった場合は、わざわざCloudFront Functionsを使わずとも該当WAFにRuleを追加する、というかたちのほうがコストを抑えながら実現できるかもしれません。またCloudFront Functionsですでに特定の処理を実行しており、今回のようなHostヘッダをチェックするコードを追加すると処理が煩雑になる、といった場合にはAWS WAF側で実現するというのも選択肢になりうるかと思います。コスト面を考慮しつつ、実際の環境にあわせた適切な実現方法を採るようにしましょう。

AWS WAFでの実現方法との比較をしてみましたが、そのほかにもオリジンサーバ側でHostヘッダをチェックする、という方法も採れますね。オリジン側でALBを使用していれば、ALBリスナールールでHostヘッダをチェックすることもできるかと考えます。(ALBへのアクセスを特定のHostヘッダのみに限定する方法の一例 | DevelopersIO)ただし、CloudFront側でHostヘッダをオリジンリクエストに含め、かつキャッシュキーとなるよう設定しておく必要がある点には注意しましょう。この点を考えると、CloudFront Distributionに関連付けたAWS WAFやCloudFront FunctionsのViewer requestなど、オリジンにリクエストが到達する前に処理するほうがシンプルになるかもしれません。

ということで、ざっと思いついたCloudFront Functions以外でのcloudfront.netサブドメインからのアクセスをブロックする方法との比較でした。このほかにも実現方法はあるかもしれません。

まとめ

独自ドメインを設定したCloudFront Distributionに対して、デフォルトのd111111abcdef8.cloudfront.net形式のドメインでアクセスできないようにCloudFront Functionsを用いて設定してみました。自動的に割り当てられるcloudfront.netのサブドメインでアクセスした場合には独自ドメインにリダイレクトさせる、といったことも実現可能です。デフォルトのドメイン名でCloudFront Distributionにアクセスさせたくない場合は設定を検討してみましょう。またCloudFront Functions以外の方法でも実現可能なので、コストや構成のシンプルさなどを考慮し最適な方法を選択しましょう。