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

tl;dr

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

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

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

共通

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

  • 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

Behavior Settingsでは、

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

cf2.png

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

cf3.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

また、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
  4. 適宜ブランチを変更する (gh-pages を表示する設定であれば、gh-pages に変更)
  5. 新しいリポジトリにpushする
    • git push --set-upstream https gh-pages (gh-pages の場合)
  6. 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

Behavior Settingsでは、

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

cf5.png

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

cf6.png

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

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

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

(再掲)
r53.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を無効にして切り替える」は難しい認識です。

続きを読む

AWS CLIでアカウントIDを取得する

シェルスクリプトで何かしたいとき、アカウントIDがさくっととれると助かる場合がありますよね :relaxed:
以下で取得可能です。

$ ACCOUNT_ID=`aws sts get-caller-identity --query 'Account' --output text`;
$ echo $ACCOUNT_ID;
123456789012

補足

aws sts get-caller-identity は、APIの呼び出しを行った IAM Identity の情報を返却します。
これを使うことで、どのアカウントで呼び出しがされたものかがわかるので、複数アカウントを使い分けているようなプロジェクトでも認証情報を別に管理する必要がなくなります。

IAM User

<GetCallerIdentityResponse xmlns="https://sts.amazonaws.com/doc/2011-06-15/">
  <GetCallerIdentityResult>
    <Arn>arn:aws:iam::123456789012:user/Alice</Arn>
    <UserId>AKIAI44QH8DHBEXAMPLE</UserId>
    <Account>123456789012</Account>
  </GetCallerIdentityResult>
  <ResponseMetadata>
    <RequestId>01234567-89ab-cdef-0123-456789abcdef</RequestId>
  </ResponseMetadata>
</GetCallerIdentityResponse>

AssumeRoleで取得したセッション場合

別のアカウントからAssumeRoleした場合は、Roleを引き受けた先のアカウントとなります。また、ARNではロール名、セッション名が含まれます。

<GetCallerIdentityResponse xmlns="https://sts.amazonaws.com/doc/2011-06-15/">
  <GetCallerIdentityResult>
     <Arn>arn:aws:sts::123456789012:assumed-role/my-role-name/my-role-session-name</Arn>
    <UserId>AKIAI44QH8DHBEXAMPLE:my-role-session-name</UserId>
    <Account>123456789012</Account>
  </GetCallerIdentityResult>
  <ResponseMetadata>
    <RequestId>01234567-89ab-cdef-0123-456789abcdef</RequestId>
  </ResponseMetadata>
</GetCallerIdentityResponse>

GetFederationTokenで取得したセッションの場合

<GetCallerIdentityResponse xmlns="https://sts.amazonaws.com/doc/2011-06-15/">
  <GetCallerIdentityResult>
    <Arn>arn:aws:sts::123456789012:federated-user/my-federated-user-name</Arn>
    <UserId>123456789012:my-federated-user-name</UserId>
    <Account>123456789012</Account>
  </GetCallerIdentityResult>
  <ResponseMetadata>
    <RequestId>01234567-89ab-cdef-0123-456789abcdef</RequestId>
  </ResponseMetadata>
</GetCallerIdentityResponse>

Ref

続きを読む

S3上のオブジェクトをダウンロードせずに結合したい

概要

そもそもそのような使い方は想定されていないので制限があるけど、要件がマッチするなら便利かもしれない。

  • Multipart Upload (Copy) を利用することで、S3側で複数のオブジェクトを1つのオブジェクトに結合が可能。ローカルへのダウンロード、アップロードを行わずに実施できる。
  • ただし、マルチアップロードの制限に準ずるため、各オブジェクトは 5 MB 以上である必要がある。 (末尾のオブジェクトは5MB未満もOK)

ざっくり手順

  1. マルチパートアップロードの初期化
    aws s3api create-multipart-upload --bucket <dest_bucket> --key <dest_key>
  2. パーツのコピー
    aws s3api upload-part-copy --bucket <dest_bucket> --copy-source <src_bucket>/<src_key> --key <dest_key> --part-number 1 --upload-id <upload_id>
  3. マルチパートアップロード完了

    aws s3api complete-multipart-upload --bucket --key <dest_key> --upload-id <upload_id> --multipart-upload "Parts=[{ETag=ETag1,PartNumber=1},{ETag=ETag2,PartNumber=2}]"

詳細

マルチパートアップロード初期化

作成先のキーを指定して、UploadId を取得します。

aws s3api create-multipart-upload --bucket <dest_bucket> --key <dest_key>

{
    "Bucket": "<dest_bucket>",
    "UploadId": "pc7Gsw_6jFitDrTeqcmB...kLJnG77fkrQgpVqJAI9nEaiSjg--",
    "Key": "<dest_key>"
}

パーツのコピー

結合対象のオブジェクトをコピーしていきます。
--copy-source でコピー元を指定します。
ETag が返却されるのでメモします。

aws s3api upload-part-copy --bucket <dest_bucket> --copy-source <src_bucket>/<src_key> --key <dest_key> --part-number 1 --upload-id <upload_id>

{
    "CopyPartResult": {
        "LastModified": "2016-11-13T01:45:08.000Z",
        "ETag": ""ec8bb3b24d5b0f1b5bdf8c8f0f541ee6""
    }
}

aws s3api upload-part-copy --bucket <dest_bucket> --copy-source <src_bucket>/<src_key> --key <dest_key> --part-number 2 --upload-id <upload_id>

{
    "CopyPartResult": {
        "LastModified": "2016-11-13T01:45:14.000Z",
        "ETag": ""25e317773f308e446cc84c503a6d1f85""
    }
}

ここではオブジェクトのコピーする位置も指定可能です。本来5GBを超えるオブジェクトをコピーするために利用されるので、この場合は当然ながら5GB以下のレンジを指定してマルチパートとしていきます。

マルチパートアップロード完了

--multipart-upload には、全てのパーツを PartNumberETag を指定して列挙する必要があります。

aws s3api complete-multipart-upload --bucket <desy_key> --key <dest_key> --upload-id <upload_id> --multipart-upload "Parts=[{ETag=ec8bb3b24d5b0f1b5bdf8c8f0f541ee6,PartNumber=1},{ETag=25e317773f308e446cc84c503a6d1f85,PartNumber=2}]"
{
    "ETag": ""4998c056afc65f55ea2ccf3a4714b5e5-2"",
    "Location": "https://<dest_bucket>/<dest_key>",
    "Bucket": "<dest_bucket>",
    "Key": "<dest_key>"
}

続きを読む

カテゴリー 未分類 | タグ