🐥

Hello AWS (part 2:HTTPS対応と公開設定)

に公開

動機

Part 1ではFargateタスクのIPアドレスを(AWS側に)適当に振り分けてもらい、それをそのまま(http://xxx.xxx.xxx.xxxのように)使用してサービスにアクセスしていた。
この方法はAWSへのHello worldとしては問題ないが、実用上は以下の意味で好ましくない。

  • AWSの都合でIPアドレスが変化してしまう。
  • アドレスが32ビットの数字なので、なんのサイトなのかをユーザに知らせることができない。

またHTTPプロトコルは一切の暗号化を行なっていないため、パスワードなどの秘匿すべき情報のやり取りができない。

目的

  • Part 1で構築したサイトに、固定したドメインを使ってHTTPSプロトコルでアクセスできるようにする。より具体的に言えば
    • Application Load Balancerを使用し、Fargateのタスクに固定ドメインでアクセスできるようにする。
    • Route 53を使用し、好きなドメイン名を用いてアクセスできるようにする。
    • AWS Certificate Managerを使用してドメインを認証することで、HTTPSを用いた秘匿性の高い通信を行えるようにする。
  • 様々な部分をPart 1よりセキュアな構成に置き換える。例えばパブリックサブネットとプライベートサブネットを適切に使い分けることで、FargateタスクにパブリックIPを割り当てず、直接インターネットからアクセスされない構成にする。

免責

内容の正確性に注意を払ってはいますが、不正確な理解による不正確な記述があり得ます。
定期的に見直し改善していく予定ですが、その点注意して読んで頂ければ幸いです。

構成

初めに構成図を載せる。
赤矢印がリクエストの流れ、青矢印がそれ以外(AWSサービスからの通信など)を表す(青はレスポンスの流れではない)。

Part 1で使用したものに加え、更に以下のサービスを利用する。

Application Load Balancer (ALB)

ECS Fargateタスクはサーバレス環境で稼働するので、個々のタスクに固定のIPアドレス(パブリック、プライベート共に)やDNS名を付与することはできない。
またタスクはスケールに応じて動的に起動・停止されるので、そもそも固定のIPアドレスやDNS名を付与する対象とするのはおかしい。

したがって

  • 常に一定のドメイン名を保持する
  • Fargateタスクのアドレスをモニタし、仮にタスクが動的に変化した場合でも自動でルーティングする

ような仕組みが別で必要であり、今回はApplication Load Balancer(ALB)を利用する。

ドメイン名について

ALBを用いることで、xxx-alb-yyyyyyyyy.ap-northeast-1.elb.amazonaws.comのような固定ドメインを生成し保持することができる。
このDNSは固定であり、Fargateタスクが増減するようなケースでも影響されない。

ルーティングについて

ALBは他の多くのサービスと同様にVPCに配置され、以下のような構成要素によって動作する。

  • リスナ(listener)
    リクエストを受け付けるプロトコル及びポートを定義する。例えば今回ならHTTP(80)やHTTPS(443)で、ここに対するリクエストがルーティングされる。リスナには「特定のパスに来たリクエストはこのターゲットグループへ転送する」といったルールを設定でき、例えば今回はHTTPリクエストをHTTPSに転送する設定を行っている。
  • ターゲットグループ(Target group)
    ALBによるリクエストの転送先。Fargate タスクがここに「登録」される。ECSサービスを作成する際にこのターゲットグループと連携させることで、自動的にタスクの作成・削除時にIPアドレスが登録・解除されるようになる。
  • ヘルスチェック
    タスクがリクエストを受信できる状態かを定期的に調べる。タスクがUnhealthyと判断された場合、ALBはそのタスクへのリクエスト転送を行わない。

ALB、ECSタスク、サブネットの関係

ALBはDNS名をインターネットに公開し、IGW経由でアクセスを受け付ける必要があるので、パブリックサブネット上に配置される。

一方でFargateタスクのIPアドレスを直接外部に公開する必要がない場合、Fargateタスクはプライベートサブネットに置くのがセキュリティ的に好ましい(FargateタスクにパブリックIPアドレスを付与する必要がなくなる)。
この場合でもALBはFargateタスクの場所をVPC内部の(=プライベートな)IPアドレスを用いて解決できるため、通信上問題はない。

Route 53

ALBによって生成されたドメイン(xxx-alb-yyyyyyyyy.ap-northeast-1.elb.amazonaws.com)は固定なので、ここに対して直接リクエストしてもアクセスは可能である。
ただコレを直接公開する代わりに自身の好きなドメイン(例えばhello-aws.my-project.jp)を紐づけてそちらを公開するのが一般的であり、このドメインのマッピングを行うサービスがRoute 53である。

ドメインは事前にどこかで取得しておく必要がある(AWSでも取得可)が、カスタムドメインだとだいたい15USD/yearくらいかかるようである。

設定はドメインの前半と後半に分けて行う。

まず後半、上の例でいうところのmy-project.jpを設定する必要がある(hosted zoneと呼ばれる)。その上で、hello-aws.my-project.jpのようなサブドメイン(recordと呼ばれる)を追加していく。
(私は今回すでに取得されたルートドメインを使用したので、hosted zoneの設定は行なっていない。)
なおレコードは当然一つのhosted zoneに対して複数持ちうる。

レコードの設定におけるValueとして、上で得られたALBのアドレスを設定する。
Record typeは今回はAWSのリソースを指すアドレスなので、AもしくはCNAMEを設定できる。

  • Aのとき
    Aliasとして該当のリソースをプルダウンから選択する(dualstack.xxx-alb-yyyyyyyyy.ap-northeast-1.elb.amazonaws.com.などが入る)。
  • CNAMEのとき
    単純にALBが示すDNS(xxx-alb-yyyyyyyyy.ap-northeast-1.elb.amazonaws.com)を指定すれば良い。

AWS Certificate Manager (ACM)

HTTPSプロトコルで通信するには、サーバが「このドメインの正当な所有者である」ことを証明するための証明書(SSL/TLS 証明書)が必要になる。
AWSでは、この証明書を簡単に発行・管理できるサービスとしてAWS Certificate Manager(ACM)を提供している。

認証の申請

認証の申請は簡単で、Route 53で登録したドメイン(今回ならhello-aws.my-project.jp)をコンソールから申請するだけである。
DNS validationという手続きが必要だが、これも指示にしたがって一時ドメインをRoute 53に登録するだけである(この一時ドメインは認証が発行された後で削除しても問題ない)。

ALBの変更

認証が発行されたらHTTPSをlistenするようにALBを設定する。すなわち

  • Protocol: HTTPS
  • Port: 443
  • SSL certificate: 発行されたものをプルダウンから選択
  • Default action: HTTPと同じtarget groupに転送

の設定を行う。

なおHTTPSのルールを設定してもユーザはHTTPでリクエストを送ってくる場合もあるが、そういった場合にはHTTPSへアクセスするように促すのが望ましい。
一つの方法としてHTTPリクエストにはHTTPSへのリダイレクト要求を返すというものがあり、今回はこれを設定する。
具体的にはHTTPリスナの「デフォルトアクション」をHTTPSへのリダイレクトに設定する。
これによりユーザがhttp://にアクセスしても、(ブラウザ経由のアクセスであれば)自動的にhttps://に切り替わる。

リクエストの流れ

Part 1と比べてECS内部の流れは特に変化しておらず、ALBが間に挟まって仲介するようになっている。

  1. クライアントが https://hello-aws.my-project.jp にリクエストを送信する。Route 53によってALBの指定したドメイン(xxx-alb-yyyyyyyyy.ap-northeast-1.elb.amazonaws.com)に送られる。
  2. ALBがリクエストを受け取り、登録済みのターゲット(ECSタスク)にルーティングする。
  3. ECSタスク内のNginxがHTTPレスポンスを返却。
  4. レスポンスはALBを経由してクライアントに返却される。

Part 1からの改良点

以下に述べる内容はPart 1ですでに構築されていた構成の一部をセキュリティや責任分離の観点から改善したものである(上述の内容と一部重複している)。

特に重要視するのはFargateタスクの外部との通信である。
Part 1では、FargateタスクがVPC外部と通信するため(DockerイメージをECRからpullするため)に、FargateタスクにパブリックIPアドレスを付与していた。
この方法は簡単だが、不必要に大きな権限を与えてしまっている。

今回はALBの採用でECSタスクをパブリックに公開する必要がなくなったことを利用して、次のようにセキュリティと責任分離を強化する:

  • セキュリティグループをALBからの通信のみに限定する
  • ECSタスクをプライベートサブネットに配置し、パブリックIPを与えない
  • NAT Gatewayを経由してアウトバウンド通信を実現する

これらの設定により通信ルートを明示的に制御したよりセキュアな構成が実現できる。

Security group(SG)の作成と変更

ALBを導入したことでECSとALBそれぞれに対してSGが設定できるようになった。
同一のSGを適用しても動作自体はするが、それぞれの役割に沿って適切なinbound、outboundルールを設定することにする。

  • ALB - inbound
    ALBはインターネットに面しており、クライアントからのリクエストを直接受け取る。そのためHTTP通信を受け付けるための設定をする。
    • Type: HTTP
    • Protocol: TCP
    • Port range: 80
    • Source: 0.0.0.0/0
      個人用のsandboxとしてでよいのならソースは自身のIPのみ、などとするのが好ましい。
  • ALB - outbound
    ALBは受け取ったリクエストをECSタスクに転送するので、ECSタスクのSGを宛先としたHTTP通信を許可する。
    • Type: HTTP
    • Protocol: TCP
    • Port range: 80
    • Destination: ECSのセキュリティグループ
  • ECS - inbound
    ECSタスクはALBからのリクエストのみを受け入れるため、ALBのSGからの通信のみを許可する。
    • Type: HTTP
    • Protocol: TCP
    • Port range: 80
    • Soruce: ALBのセキュリティグループ
  • ECS - outbound
    ECRからDockerイメージをpullするため、ECSコンテナから外部への通信は全て許可している。これもpermissiveすぎるので限定するべきである(ToDo)。
    • Type: All trafic
    • Protocol: All
    • Port range: All
    • Destination: 0.0.0.0/0

通信が正しく許可・制限されているかはAWSコンソールのLoad Balancersページで該当LBResource mapにおいてグラフィカルに確認できる。

Private subnetとNAT gateway

ユーザからのリクエストをインターネット経由で受け付けるのはALBの責務である。
FargateタスクはALBによって転送されたリクエストを処理し、レスポンスをALBに返却するだけなので、Fargateタスク自体が直接インターネットとやりとりを行う必要はない。
したがってECSの所属するサブネットはパブリックである(IGWへのルーティングを持つ)必要はない。

一方でFargateタスクは起動時にDockerイメージをECR(外部)から取得する必要があり、これはインターネットとの通信(outbound)を伴う。

これを扱うために、以下のような構成を取る:

  • Fargateタスクはプライベートサブネット(IGWへのルートを持たない)に配置し、ルートテーブルには0.0.0.0/0NAT Gatewayに向けるルートを追加する
  • NAT Gatewayをパブリックサブネットに配置する

これにより、Fargateタスク自身にはパブリックIPを付与せず、かつ必要なときだけNAT Gateway経由でインターネットに出られる構成を実現できる。

補足

今回はあくまでIPアドレスを固定することを主眼とし、1つのECSサービス(Nginx)のみをALBで扱った。
ALBは複数のターゲットグループを扱うこともできるので、サービスごとに異なるルールやターゲットグループを設定することもできる。
複数のサービスを立ち上げ、運用する構成はPart 3で扱う。

Discussion