CloudFrontを利用して独自ドメインのGitHub PagesをHTTPS化する

## tl;dr

方法として2つあるが、設定がシンプルであることと、エッジ-オリジン間もHTTPS化できるため後者がオススメ。

1. CNAME を有効のままで、CloudFrontで `Host` ヘッダを転送する設定とする
2. CNAME を無効にして、 `https://example.github.io/reponame/` でアクセスするようにし、`/reponame/` をOriginのパスとして利用する

Zone Apex (サブドメイン無し) の場合でも、どちらでも可能。

- [Setting up an apex domain - User Documentation](https://help.github.com/articles/setting-up-an-apex-domain/)

## 共通

以下の例で進めていきます。

- Default Page Domain: `username.github.io`
- Repository Name: `reponame`
- CNAME: `reponame.example.jp`

- SSL証明書は、事前にACMで用意しておいてください。
  - ACMで証明書を作成する際は `us-east-1` (N.Virginia) リージョンで作る必要があるのでお忘れなく。
- また、Route53でDNSを管理していることを前提として説明しますが、別のDNSサービスでも手順は変わるものの実現可能です。
- 切り替えるレコードのTTLは切り替えがなる早で反映されるように一時的に短くしておいてください

## CNAMEを有効のまま設定する

GitHub Pages 側をCNAMEを有効にしたままでCloudFrontを設定し切り変えます。

`http://reponame.example.jp` で GitHub Pages が開ける状態となっている前提で進めていきます。

1. CloudFrontディストリビューションを `Host` ヘッダを転送する設定で作成する
2. Route53のレコードをCloudFrontに向ける

### CloudFrontディストリビューションの作成

Origin Domain Name を `username.github.io` に

![cf1.png](https://qiita-image-store.s3.amazonaws.com/0/19607/7f05cc19-e9f6-2520-204a-cac8b1506bb5.png)

Behavior Settingsでは、

- `Redirect HTTP to HTTPS` で HTTP でアクセスしたときに HTTPS にリダイレクトするようにする
- `Forward Headers` で `Whitelist` を設定し、`Host` を追加する
- Compress Object Automatically はお好みで。静的テキストコンテンツが多くなる傾向があると思うので有用だと思います

![cf2.png](https://qiita-image-store.s3.amazonaws.com/0/19607/227c86db-9eba-8a3a-0174-25330d8af267.png)

そして、Distribution Setting では、予めACMに作成しておいたCNAMEにマッチする証明書を設定します。

![cf3.png](https://qiita-image-store.s3.amazonaws.com/0/19607/c39a1033-ab31-2d39-37cf-1cdc3d5da7ee.png)

これでディストリビューションを作成し、利用可能になるまでしばし待ちます。
作成したDistributionのドメイン名で、 `https://dxxxxxxxxxxxxx.cloudfront.net` という形でアクセスできるようになります。

### 動作確認

ドメインの名前解決先を切り替えてしまうと、問題があった場合に困ったことになるので動作確認します。

#### Hostsで確認

先程作成したDistributionのドメイン名 (`dxxxxxxxxxxxxx.cloudfront.net`の形式) をnslookupします。

```
nslookup dxxxxxxxxxxxxx.cloudfront.net
Server:		192.168.179.1
Address:	192.168.179.1#53

Non-authoritative answer:
Name:	dxxxxxxxxxxxxx.cloudfront.net
Address: 13.32.230.52
Name:	dxxxxxxxxxxxxx.cloudfront.net
Address: 13.32.230.163
Name:	dxxxxxxxxxxxxx.cloudfront.net
Address: 13.32.230.25
Name:	dxxxxxxxxxxxxx.cloudfront.net
Address: 13.32.230.78
Name:	dxxxxxxxxxxxxx.cloudfront.net
Address: 13.32.230.12
Name:	dxxxxxxxxxxxxx.cloudfront.net
Address: 13.32.230.186
Name:	dxxxxxxxxxxxxx.cloudfront.net
Address: 13.32.230.102
Name:	dxxxxxxxxxxxxx.cloudfront.net
Address: 13.32.230.208
```

1つ適当に選んで、Hostsに書きます。

```
reponame.example.jp    13.32.230.52
```

ブラウザを開いて `http://reponame.example.jp` にアクセスし、HTTPSにリダイレクトされること、HTTPSで正常にアクセスできることを確認します。
CloudFrontのIPは逐次変わるため、永続的にこのIPで動作確認できるわけではないことにご注意ください。
動作確認が終わったら確実にHostsエントリを削除しましょう。

### curlで確認

`--connect-to` が便利です!

- `-v`: デバッグ表示
- `--silent`: プログレスバーを表示しない
- `--location`: リダイレクトに従う
- `--connect-to:HOST1:PORT1:HOST2:PORT2`: `HOST1:PORT1` へのリクエストを `HOST2:PORT2` に送る。この例では、`reponame.example.jp` へのリクエストを `dxxxxxxxxxxxxx.cloudfront.net` に送る。

リダイレクトして、HTTPSでレスポンスがCloudFrontから返却されていることを確認する。

```
curl -v --silent --location --connect-to "reponame.example.jp::dxxxxxxxxxxxxx.cloudfront.net:" http://reponame.example.jp/ > /dev/null
* Connecting to hostname: dxxxxxxxxxxxxx.cloudfront.net
*   Trying 13.32.230.25...
* TCP_NODELAY set
* Connected to dxxxxxxxxxxxxx.cloudfront.net (13.32.230.25) port 80 (#0)
> GET / HTTP/1.1
> Host: reponame.example.jp
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 301 Moved Permanently
< Server: CloudFront
< Date: Sun, 10 Sep 2017 10:06:49 GMT
< Content-Type: text/html
< Content-Length: 183
< Connection: keep-alive
< Location: https://reponame.example.jp/
< X-Cache: Redirect from cloudfront
< Via: 1.1 78dc5acc7fb7b026e9215d8188becd98.cloudfront.net (CloudFront)
< X-Amz-Cf-Id: ymg3bDg7MThc2VVOuIpOC3r42GyuRDXJUS0HnB9cKnWWTnRjWD-e_g==
<
* Ignoring the response-body
{ [183 bytes data]
* Connection #0 to host dxxxxxxxxxxxxx.cloudfront.net left intact
* Issue another request to this URL: 'https://reponame.example.jp/'
* Connecting to hostname: dxxxxxxxxxxxxx.cloudfront.net
*   Trying 13.32.230.25...
* TCP_NODELAY set
* Connected to dxxxxxxxxxxxxx.cloudfront.net (13.32.230.25) port 443 (#1)
* TLS 1.2 connection using TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
* Server certificate: reponame.exmple.jp
* Server certificate: Amazon
* Server certificate: Amazon Root CA 1
* Server certificate: Starfield Services Root Certificate Authority - G2
> GET / HTTP/1.1
> Host: reponame.example.jp
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Type: text/html; charset=utf-8
< Content-Length: 18280
< Connection: keep-alive
< Server: GitHub.com
< Last-Modified: Sat, 09 Sep 2017 13:30:08 GMT
< Access-Control-Allow-Origin: *
< Expires: Sun, 10 Sep 2017 10:04:44 GMT
< Cache-Control: max-age=600
< X-GitHub-Request-Id: F24E:8856:173BB3:19374E:59B50BE4
< Accept-Ranges: bytes
< Date: Sun, 10 Sep 2017 09:54:44 GMT
< Via: 1.1 varnish, 1.1 45b893b1fe0ecafde6bfa63bc4abc859.cloudfront.net (CloudFront)
< X-Served-By: cache-nrt6125-NRT
< X-Cache-Hits: 0
< X-Timer: S1505037284.497836,VS0,VE232
< X-Fastly-Request-ID: 27614a7c60d3bb6edab88b25f86c9a21cc4b3899
< Vary: Accept-Encoding
< Age: 92
< X-Cache: Hit from cloudfront
< X-Amz-Cf-Id: My_BZNmrPJFeHIZsTST_5gc5A5jvk7XXVSdErVcAxhvxtfUAVud4PQ==
<
{ [15630 bytes data]
* Connection #1 to host dxxxxxxxxxxxxx.cloudfront.net left intact
```

### Route53の変更

`example.jp` のHosted Zoneを開いて、`reponame.example.jp` のエントリを更新します。

![r53.png](https://qiita-image-store.s3.amazonaws.com/0/19607/9d5f807a-3528-f65b-bf94-2d6066f78b9b.png)

また、AAAAレコードも同様に設定することで、IPv6対応になります。


## CNAMEを無効にするパターン

こちらはGitHub PagesでCNAMEが有効になっているのを無効にする方法です。
停止期間が無いように、一時的にリポジトリをコピーし、また、別ドメインでテストをできるようにします。


1. GitHubのリポジトリを一時的に作成した別のリポジトリにコピーし、GitHub Pagesを表示できるようにする
2. CloudFrontディストリビューションを作成
3. Route53でテストドメインの設定
4. テストドメインで動作確認
5. Route53で本番の切り替え

### リポジトリのコピー

やり方はいろいろありますが、一例。

1. 対象のリポジトリ (`reponame`) をローカルにcloneする
2. GitHubで新しいリポジトリを作る (`reponame-https-tmp`)
3. リモートを追加する 
  - `git add remote https git@github.com:username/reponame-https-tmp.git
`
- 適宜ブランチを変更する (`gh-pages` を表示する設定であれば、`gh-pages` に変更)
- 新しいリポジトリにpushする
  - `git push --set-upstream https gh-pages` (`gh-pages` の場合)
- `CNAME` ファイルが存在する場合は、新しいリポジトリ側で削除する

Settings で GitHub Pages が表示されるよう設定し、`https://username.github.io/reponame-https-tmp/`
で表示できることを確認する。
(リンクや埋め込んだ画像等が `/hoge` とか絶対パスになって、うまく表示できないかもしれないですが、それらは一旦スルー)

### CloudFrontディストリビューションの作成

まず、新しく作成したリポジトリに向けたディストリビューションを作成します。

Origin Settings では

- Origin Domain Name を `username.github.io` に
- Origin Path を 、`/reponame-https-tmp/` に
- Origin Protocol Policy は HTTPS Only にします

![cf4.png](https://qiita-image-store.s3.amazonaws.com/0/19607/a122bc8b-1df8-01bc-eb12-adb4e56845ce.png)

Behavior Settingsでは、

- `Redirect HTTP to HTTPS` で HTTP でアクセスしたときに HTTPS にリダイレクトするようにする
- Compress Object Automatically はお好みで。静的テキストコンテンツが多くなる傾向があると思うので有用だと思います

![cf5.png](https://qiita-image-store.s3.amazonaws.com/0/19607/48c90e08-3086-f5e7-dfd8-27951bc8e389.png)


そして、Distribution Setting では、CNAMEs に切り替え予定の `reponame.example.jp` とは別に動作確認用の `reponame-test.example.jp` といったCNAMEも付与します。
そして、ACMに作成しておいたCNAMEにマッチする証明書を設定しますが、この証明書は `*.example.jp` のワイルドカードかAdditonal Namesを使って両方をカバーしたものを用意、適用します。

![cf6.png](https://qiita-image-store.s3.amazonaws.com/0/19607/1f36b429-f9c1-fc44-edb0-950dbb0ca1a2.png)

これでディストリビューションを作成し、利用可能になるまでしばし待ちます。

### Route53でテストドメインの設定

- Route53で先ほど作成した動作確認用のアドレス (`reponame-test.example.jp`) からCloudFrontディストリビューションへのエイリアスを作成します。

(再掲)
![r53.png](https://qiita-image-store.s3.amazonaws.com/0/19607/9d5f807a-3528-f65b-bf94-2d6066f78b9b.png)

### 動作確認

`http://reponame-test.example.jp/` がHTTPSにリダイレクトされること、`https://reponame-test.example.jp/` が表示されることを確認する。


wgetでクロールさせて、デッドリンクや、`github.io` 側に移動してしまったりが無いかを確認するのもよいです。

```
wget --spider --recursive --no-directories --no-verbose https://reponame-test.example.jp/
```


### Route53で本番の切り替え

続いて、一時的に `https://reponame.example.jp` を、 `reponame-https-tmp` 側を表示するよう切り替えます。

- Route53で本番のドメイン (`reponame.example.jp`) から作成したCloudFrontディストリビューションへのエイリアスを作成します。

### 元リポジトリの設定変更

`reponame.example.jp` がCloudFrontに向くようになって、切り替える前のレコードのTTLが十分経過してから、元リポジトリの設定を変更し、`https://username.github.io/reponame/` でアクセスできるようにします。

- CNAME ファイルがあれば削除する
- Settings で独自ドメインの設定を無効にする

### CloudFrontのオリジンの追加、設定変更

1. OriginsタブのCreate Originから `https://username.github.io/reponame/` を指すオリジンを新しく作成する。
2. Behaviorsタブで、デフォルトのBehaviorにチェックを入れて、Editをクリックし、Origin を新しく作った本番 (`reponame`) に向いたものに変更し、"Yes, Edit" で反映する
3. 設定が反映されるのを待ち、その後、変わらずにページが表示できることを確認する
4. 最初に作ったオリジンを削除する

### 後片付け

- Route53のテストドメインを削除する
- 一時的に作ったリポジトリを削除する


## 備考

GitHub Pages 以外のサイトにも応用可能ですが、CNAMEが正しく設定しているか定期的に確認し、設定されていないと独自ドメイン設定を解除するサービスでは、CNAME設定のまま切り替えをするパターンだと後々表示されなくなるので注意です。
設定画面でCNAMEが正しく設定されているか確認してくれるサイトは、この可能性が高いです。

また、独自ドメインでない場合にページ内のリンクのURLが変わってしまうようなサービスでは、上記「CNAMEを無効にして切り替える」は難しい認識です。