【VueSlsApp】CloudFrontでHTTPS化してみた

こんにちは。てぃろです。

今回はVueSlsAppをCloudFrontを使ってHTTPS化したいと思います。今のままではフロントアプリがHTTPのままでログインさせるには危ないからです…!当然ですが、従来のHTTPアクセスも禁止していきます。主に使うツールは、Terraformです。

アプリの紹介記事は以下。

デプロイしてあるものは、以下から実際に触ってみてもらえます。
・HTTP -> http://dev-vueslsapp.thiroyoshi.com/ (もうアクセスできない)
・HTTPS -> https://vueslsapp.thiroyoshi.com/

アプリのソースは以下で公開しています。

CloudFront構築のスクリプトは以下です。

本記事を読む前に

本記事は、以下の記事を読んで本アプリのアーキテクチャをなんとなくでも理解いただいている方を対象としています。

また、実際にソースをデプロイして動かしてもらえるように作っていますので、上記で紹介しているソースは事前にcloneして、手元で確認しながら読んでもらえると理解が進みやすいと思います。

一番よいのは、以下の記事でアプリのデプロイをしてもらっていることですが、必ずしもそうしていなくても理解だけはしてもらえると思います。

HTTPS化されたアーキテクチャ

以下が新しいアーキテクチャです。こちらでS3への直接のアクセスをなくしていることが今回のHTTPS化の効果です。

画像2

これまでのアーキテクチャは以下のようになっていて、CloudFrontがなかったことがよくわかります。

画像3

HTTPS化でやることは、大きく4つ

今回は、Terraformを使います。Infrastructure as Codeを実現するツールの一つとして以前から愛用しています。

Terraformで主にやることは以下の3つです。

・CloudFrontそのものを立てる
・アプリがデプロイされているS3と接続する
・CloudFrontのエンドポイントにドメインをあてる

他にTerraformではなく、アプリのServerless Frameworkで以下をします。

・S3の静的ウェブホスティングを無効化

これはCloudFront以外からのアクセスを禁止して、必ずHTTPSの経路を通ってもらうために設定を破棄します。

S3の静的ウェブサイトホスティングを無効化する

あとでCloudFrontを立てた時にも、S3のバケットポリシーをいじることになるので、まずはこちらを実施してバケットポリシーをきれいにしておきます。

こちらのソースの中の、vueslsapp/sls_configurations/sls_web.ymlの以下のようにコメントアウトしてしまいます。つまり、BucketNameだけ残します。

 Resources:
 ## Specifying the S3 Bucket
 StaticSite:
   Type: AWS::S3::Bucket
   Properties:
     # AccessControl: PublicRead
     BucketName: ${self:custom.siteName}
     # WebsiteConfiguration:
     #   IndexDocument: index.html
     #   ErrorDocument: index.html

 # StaticSiteS3BucketPolicy:
 #   Type: AWS::S3::BucketPolicy
 #   Properties:
 #     Bucket:
 #       Ref: StaticSite
 #     PolicyDocument:
 #       Statement:
 #         - Sid: PublicReadGetObject
 #           Effect: Allow
 #           Principal: "*"
 #           Action:
 #           - s3:GetObject
 #           Resource:
 #             Fn::Join: [
 #               "", [
 #                 "arn:aws:s3:::",
 #                 {
 #                   "Ref": "StaticSite"
 #                 },
 #                 "/*"
 #               ]
 #             ]

 # DnsRecord:
 #   Type: "AWS::Route53::RecordSet"
 #   Properties:
 #     AliasTarget:
 #       DNSName: ${self:custom.aliasDNSName}
 #       HostedZoneId: ${self:custom.aliasHostedZoneId}
 #     HostedZoneName: ${self:custom.domain}.
 #     Name:
 #       Ref: StaticSite
 #     Type: 'A'

# Outputs:
#   StaticSiteS3BucketName:
#     Value:
#       'Ref': StaticSite
#   StaticSiteS3BucketDomainName:
#     Value:
#       Fn::GetAtt:
#         - StaticSite
#         - DomainName
#   StaticSiteS3BucketWebsiteURL:
#     Value:
#       Fn::GetAtt:
#         - StaticSite
#         - WebsiteURL

コメントアウトしたコードは、静的ウェブサイトホスティングの設定ドメインをあてる設定の二つです。これらはCloudFrontを使うとS3へは直接HTTPでアクセスしないようになるため不要なのです。

これをコメントアウトしたら、アプリをデプロイしましょう。デプロイが完了すると、以下図のようにアプリにアクセスできなくなっていると思います。

画像4

TerraformでCloudFrontを立てて、S3とつなぐ

次にCloudFrontを立てつつ、S3へのアクセス権を付けていきます。そのためのソースコードは以下です。

上記のソースでやっていることは、以下です。

・CloudFrontアクセスログ記録用S3バケットを作成する
・CloudFrontを立てる
・CloudFrontのエイリアスレコードとなるRoute 53のレコードを追加する
・CloudFrontからのみアクセスできるS3バケットポリシーを追加する

図でいうと、以下の赤枠で囲われた部分を作ってます。簡単のためにアクセスログ用のS3バケットは明記していません。

画像5

ソースの詳細についてはまた別の機会に解説します。

SPAをCloudFrontで公開する時には、カスタムエラーレスポンスの設定が必須

今回アプリとしてデプロイしているのは、Vue.jsで作ったいわゆるSPA(Single Page Application)です。

CloudFrontは、ブラウザからの要求をそのままS3のパスによってリソースを取得しようとします。つまり、"/login"などS3のパスには存在しないが、SPAとしては定義されているページにアクセスすることになります。ですが、何もしないとS3のリソースが見つからないために、エラーページが返されてしまいます。

そこで本来エラーページとして返すページを正常なページとして返すように設定すればよいのです。そうすると、SPAとしての動作を取り戻します。Terraformのソースとしてはcloudfront.tfの以下の部分がそのための設定になります。

  custom_error_response {
   error_caching_min_ttl = 0
   error_code = 404
   response_code = 200
   response_page_path = "/index.html"
 }

 custom_error_response {
   error_caching_min_ttl = 0
   error_code = 403
   response_code = 200
   response_page_path = "/index.html"
 }

これはCloudFrontのカスタムエラーレスポンスの設定です。必要なら404や403以外のエラーに対して他のエラーページも設定していけばよいです。ブラウザでエラーページが出たときに開発者ツールなどでステータスコードを確認すれば、どのエラーコードに対する設定が必要か確認できます。

また、正常なレスポンスとして返すとは、

・ステータスコードを200とする
・返すリソースを、/index.htmlにする

とすることを言います。

以下を参考にさせてもらいました。

削除するときは、一部手動が必要

CloudFrontのOrigin Identityの紐づけが自動で消せないらしく、CloudFrontを直接マネジメントコンソールから手動で消す必要があります。それも含めた削除の手順は以下の通りです。

1. アクセスログ用S3バケットの中身を空にする
2. terraform destroyコマンドで、一度削除を実施
3. CloudFrontが無効化されたら、マネジメントコンソールから削除実施
4. もう一度、terraform destroyコマンドで削除実施

1. の手順でS3バケットを削除できるように、中身を空にします。空にするにはマネジメントコンソールから、空にするのボタンを使うのが簡単です。

コメント 2020-04-19 165731

上記のように、S3のマネジメントコンソールの右上のあたりにあるので、削除対象のバケットを選択して、空にするを選択していきましょう。

次の2. の手順を実施しても、削除は失敗します。このときに以下のようなエラーになるはずです。

Error: CloudFrontOriginAccessIdentityInUse: The CloudFront origin access identity is still being used.

しかし、CloudFrontの無効化は実行されています。それが完了するのを待ってマネジメントコンソール上で削除を実施する、というのが3. の手順です。

CloudFrontが消えてしまえば、4. のterraformの削除は成功するはずです。

最後に

今回は、VueSlsAppをHTTPS化してセキュリティを強化してみました。

これでパスワードなどの情報を打ち込んでも安心になりました。(そもそもAPIはHTTPSなので、大丈夫といえば大丈夫ですが…)CDNを使っているので、パフォーマンスの向上なども図れます。

S3で静的に公開している、という場合に手軽に使えると思いますので、ぜひ参考にしてみてください。

おまけ

最近Terraformを使うときのお気に入りのツールで、Terraform Cloudがあります。

Terraformで一番厄介なstate管理をクラウドでやってくれるし、Githubと連携してCI/CD構築が簡単にできるので、オススメです。

いつも記事を読んでいただいてありがとうございます。少しでもあなたの人生にプラスになる話ができているとうれしいです。