2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

GenU閉域モードを深掘りする~フロントエンド・認証編~

2
Posted at

概要

GenU閉域モードで作成されるリソースや各種フローなどを細かく見ていきます。
なお今回はVPCや名前解決などの基本ネットワーク、フロントエンド、認証の箇所までを記載します。

g000.png

前提条件

閉域モードを有効化して、以下のパラメータを指定しています。

パラメータ デフォルト値 実デプロイ値
closedNetworkMode false true
closedNetworkDomainName null genu.example.local
closedNetworkCertificateArn null ACMのARN指定
modelRegion us-east-1 ap-northeast-1

closedNetworkDomainNameclosedNetworkCertificateArnは「両方指定する」か「両方指定しない」のどちらかである必要があります。今回は両方指定しているため、プライベートホストゾーンが作成され、ALBにHTTPSリスナーが構成される構成になります。

基本ネットワーク

VPCが1つ作成され、サブネットはap-northeast-1aとap-northeast-1cに各1つ、計2つ作成されます。各種VPCエンドポイントとRoute 53インバウンドエンドポイントが作成されます。

g001.png

ルートテーブルを確認すると、S3とDynamoDBのゲートウェイエンドポイントが追加されているため、マネージドプレフィックスリスト宛のルートが2つ追加されています。
インターネットゲートウェイやNATゲートウェイへのルートがない、プライベートなサブネットであることを確認できます。

g002.png

プライベートホストゾーンが作成され、作成されたVPCに紐づけが行われます。

g003.png

なお、Route 53インバウンドエンドポイントは冗長化のため2つのサブネットにIPが割り当てられます。オンプレミスのDNSサーバからフォワードする際は、このエンドポイントのIPを指定します。

フロントエンド

下記のような各種サービスが作成されます。(VPCエンドポイントは省略しています。)

g004.png

閉域モードではAmazon CloudFrontを使わず、Webの静的ファイルはALB + ECS Fargate + S3の組み合わせで配信します。

まず、オンプレミスの環境から下記トップ画面が表示されるまでの流れを見てみます。
オンプレミスから接続する際は名前解決の設定が必要ですが、下記記事の通り、オンプレミス側のDNSサーバには名前解決の設定がされているものとします。

g005.png

デプロイ時にドメイン名をgenu.example.localと指定すると、上記の通り、対応したプライベートホストゾーンが作成されています。
その中にAレコード(エイリアスレコード)が作成され、ルーティング先がALBに設定されています。genu.example.localにアクセスするとALBに接続します。

g006.png

ALBはHTTPS:443で処理を受け付けます。証明書はパラメータで設定したACMの証明書がALBに設定されています。
なお、ALBはinternalで作成されるため、閉域内からのみ到達できます。

g007.png

リソースマップで確認すると下記の通りになります。HTTPS:443で受け付けた後、ターゲットグループに転送します。
転送先のターゲットとしてFargateで稼働するコンテナの8080ポートが指定されています。

g008.png

ターゲットに表示されているIPはFargateタスクのENIに割り当てられたプライベートIPです。FargateはawsvpcネットワークモードでタスクごとにENIを持つため、ターゲットタイプはipになります。タスクの起動・停止に合わせてECSが自動でターゲットの登録・解除を行うため、手動でターゲットを操作する必要はありません。

ECSのコンソールを確認すると、クラスター、サービスの作成が確認できます。サービスの中で上記ロードバランサーが設定されていることを確認できます。

g009.png

下記タスク定義が作成され、コンテナが1つ稼働していることを確認できます。

g010.png

ECSの構成は「クラスター → サービス → タスク(タスク定義から起動)→ コンテナ」という階層になっています。FargateなのでクラスターはEC2のキャパシティを持たず、論理的なグルーピングのみです。サービスが希望タスク数を維持し続けるため、タスクが落ちても自動で再作成されます。
今回は希望タスク数1で起動し、CPU使用率とメモリ使用率をそれぞれ50%に保つターゲット追跡のAuto Scaling(1〜20タスク)が設定されています。

g015.png

コンテナからS3のWebバケットのindex.htmlを取得して、ユーザー側にトップ画面が返されます。アプリケーションのコードだと下記の箇所です。

app.get('{*key}', async (req, res) => {
  let key = req.path;
  if (key.startsWith('/')) key = key.substring(1);
  if (!key) key = 'index.html';          // ← ルートアクセスは index.html
  try {
    await s3Client.send(new HeadObjectCommand({ Bucket: BUCKET, Key: key }));
    const { Body, ContentType } = await s3Client.send(new GetObjectCommand({ Bucket: BUCKET, Key: key }));
    ...
  } catch (err) {
    // ← 存在しないパスも index.html を返す(SPA フォールバック)
    const { Body } = await s3Client.send(new GetObjectCommand({ Bucket: BUCKET, Key: 'index.html' }));
    ...
  }
});

BUCKETはprocess.env.BUCKET_NAMEです。バケット名はコードにハードコードされておらず、タスク定義の環境変数から渡されます。タスク定義の環境変数を確認すると下記S3バケット名が設定されています。

g013.png

S3コンソールを確認すると下記のとおりバケットが作成されており、index.htmlファイルがあることを確認できます。

g014.png

ここまでの流れをまとめると、以下のようになります。

認証

上記までは認証が完了していない状態のため、ここからCognitoを使用した認証の箇所を記載します。Cognitoユーザープール、IDプールが作成されています。

作成されるCognitoリソース

  • User Pool:ユーザーの「台帳」。メールアドレスとパスワードを管理する。パスワードポリシー(8文字以上・大文字・数字・記号を含む)が設定される

g011.png

  • User Pool Client:User Poolに対するアプリ側の窓口。クライアントIDを持つ。IDトークンの有効期限は1日

g202.png

  • Identity Pool:User PoolのトークンをAWSの一時クレデンシャルに変換する。これによりブラウザから直接AWSサービスを呼べるようになる

g012.png

  • IAMロール(authenticated):ログイン済みユーザーに付与されるロール。Polly、Transcribe Streaming、LambdaのInvoke権限を持つ。後述の通り、ブラウザからダイレクトアクセスするリソースに関する権限が付与されている。

g200.png

  • IAMロール(unauthenticated):未ログインユーザー用のロール。GenUでは未認証状態のアクセスを想定していないため、今回の構成では実質使われない。下記の通り権限は何も設定されていない。

g201.png

認証フロー

流れとしては下記になります。

ブラウザからCognito User Poolに対して認証を行います。今回はセルフサインアップが有効なため、初回アクセス時にメールアドレスとパスワードでサインアップします。

サインアップするとメールアドレスに確認コードが送付されるため、こちらのコードを入力して認証を完了させ、サインインを行います。
サインインが成功すると、User Poolからトークンが返却されます。

その後、ユーザープールから返却されたID Tokenを使用してCognito Identity Poolにアクセスします。
IDプールはSTSから一時クレデンシャル情報を取得してユーザーに返却します。

認証後の認証情報の使い分け

ここが少しややこしいのですが、認証後のブラウザは2種類の認証情報を使い分けています。

認証情報 使う先 認証方式
ID Token(User Poolから直接もらうJWT) API Gateway API Gatewayに設定されたCognito AuthorizerがJWTを検証する
一時クレデンシャル(Identity Pool → STS経由) Lambda直接呼び出し(ストリーミング応答)、Polly(読み上げ)、Transcribe Streaming(リアルタイム文字起こし) SigV4署名によるIAM認証

出発点はどちらも「User Poolのサインイン」ですが、受け取ったID Tokenの使い道が2つあるのがポイントです。

  • API Gatewayに送るリクエストでは、AuthorizationヘッダーにID Tokenを入れます。API Gateway側のCognito AuthorizerがJWTの署名・有効期限・発行元を検証して通す/弾くを判断します。IAMクレデンシャルは不要です。
  • 一方、ブラウザからAWS SDKで直接AWSサービスを呼ぶ場合(InvokeWithResponseStreamによるストリーミング応答など)はSigV4署名が必要なため、Identity Pool経由で取得した一時クレデンシャルを使います。

ここで再度閉域モードの構成図を見てみます。IAMロールに権限が付与されているのは下記赤枠内のブラウザから直接アクセスするサービスです。なおS3はアプリケーションから発行された署名付きURLにアクセスするため、IAM権限は不要です。(署名付きURLに認可が埋まっているため。)
その他のサービスを見ていくと、BedrockなどはLambda経由でアクセスします。DynamoDBもAPI Gateway経由でLambdaがアクセスします。API Gatewayは前述の通りAuthorizationヘッダーで認証を行うため、IAMクレデンシャルは不要です。

g203.png

まとめ

ここまでで、以下の流れが確認できました。

  1. クライアントからRoute 53のプライベートホストゾーンで名前解決し、ALB経由でFargate上のコンテナがS3から取得したトップページを返す
  2. トップページでメールアドレスとパスワードを入力し、ReactアプリからCognito User Poolを呼び出して認証し、トークンを取得する
  3. ID TokenをCognito Identity Poolに渡し、STS経由で一時クレデンシャルを取得する
  4. 以降、API GatewayへのリクエストにはID Tokenを、Lambda直接呼び出しやPolly / Transcribeには一時クレデンシャルを使う

次回はバックエンド側の処理を見ていきます。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?