概要
GenU閉域モードで作成されるリソースや各種フローなどを細かく見ていきます。
なお今回はVPCや名前解決などの基本ネットワーク、フロントエンド、認証の箇所までを記載します。
前提条件
閉域モードを有効化して、以下のパラメータを指定しています。
| パラメータ | デフォルト値 | 実デプロイ値 |
|---|---|---|
| closedNetworkMode | false | true |
| closedNetworkDomainName | null | genu.example.local |
| closedNetworkCertificateArn | null | ACMのARN指定 |
| modelRegion | us-east-1 | ap-northeast-1 |
closedNetworkDomainNameとclosedNetworkCertificateArnは「両方指定する」か「両方指定しない」のどちらかである必要があります。今回は両方指定しているため、プライベートホストゾーンが作成され、ALBにHTTPSリスナーが構成される構成になります。
基本ネットワーク
VPCが1つ作成され、サブネットはap-northeast-1aとap-northeast-1cに各1つ、計2つ作成されます。各種VPCエンドポイントとRoute 53インバウンドエンドポイントが作成されます。
ルートテーブルを確認すると、S3とDynamoDBのゲートウェイエンドポイントが追加されているため、マネージドプレフィックスリスト宛のルートが2つ追加されています。
インターネットゲートウェイやNATゲートウェイへのルートがない、プライベートなサブネットであることを確認できます。
プライベートホストゾーンが作成され、作成されたVPCに紐づけが行われます。
なお、Route 53インバウンドエンドポイントは冗長化のため2つのサブネットにIPが割り当てられます。オンプレミスのDNSサーバからフォワードする際は、このエンドポイントのIPを指定します。
フロントエンド
下記のような各種サービスが作成されます。(VPCエンドポイントは省略しています。)
閉域モードではAmazon CloudFrontを使わず、Webの静的ファイルはALB + ECS Fargate + S3の組み合わせで配信します。
まず、オンプレミスの環境から下記トップ画面が表示されるまでの流れを見てみます。
オンプレミスから接続する際は名前解決の設定が必要ですが、下記記事の通り、オンプレミス側のDNSサーバには名前解決の設定がされているものとします。
デプロイ時にドメイン名をgenu.example.localと指定すると、上記の通り、対応したプライベートホストゾーンが作成されています。
その中にAレコード(エイリアスレコード)が作成され、ルーティング先がALBに設定されています。genu.example.localにアクセスするとALBに接続します。
ALBはHTTPS:443で処理を受け付けます。証明書はパラメータで設定したACMの証明書がALBに設定されています。
なお、ALBはinternalで作成されるため、閉域内からのみ到達できます。
リソースマップで確認すると下記の通りになります。HTTPS:443で受け付けた後、ターゲットグループに転送します。
転送先のターゲットとしてFargateで稼働するコンテナの8080ポートが指定されています。
ターゲットに表示されているIPはFargateタスクのENIに割り当てられたプライベートIPです。FargateはawsvpcネットワークモードでタスクごとにENIを持つため、ターゲットタイプはipになります。タスクの起動・停止に合わせてECSが自動でターゲットの登録・解除を行うため、手動でターゲットを操作する必要はありません。
ECSのコンソールを確認すると、クラスター、サービスの作成が確認できます。サービスの中で上記ロードバランサーが設定されていることを確認できます。
下記タスク定義が作成され、コンテナが1つ稼働していることを確認できます。
ECSの構成は「クラスター → サービス → タスク(タスク定義から起動)→ コンテナ」という階層になっています。FargateなのでクラスターはEC2のキャパシティを持たず、論理的なグルーピングのみです。サービスが希望タスク数を維持し続けるため、タスクが落ちても自動で再作成されます。
今回は希望タスク数1で起動し、CPU使用率とメモリ使用率をそれぞれ50%に保つターゲット追跡のAuto Scaling(1〜20タスク)が設定されています。
コンテナから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バケット名が設定されています。
S3コンソールを確認すると下記のとおりバケットが作成されており、index.htmlファイルがあることを確認できます。
ここまでの流れをまとめると、以下のようになります。
認証
上記までは認証が完了していない状態のため、ここからCognitoを使用した認証の箇所を記載します。Cognitoユーザープール、IDプールが作成されています。
作成されるCognitoリソース
- User Pool:ユーザーの「台帳」。メールアドレスとパスワードを管理する。パスワードポリシー(8文字以上・大文字・数字・記号を含む)が設定される
- User Pool Client:User Poolに対するアプリ側の窓口。クライアントIDを持つ。IDトークンの有効期限は1日
- Identity Pool:User PoolのトークンをAWSの一時クレデンシャルに変換する。これによりブラウザから直接AWSサービスを呼べるようになる
- IAMロール(authenticated):ログイン済みユーザーに付与されるロール。Polly、Transcribe Streaming、LambdaのInvoke権限を持つ。後述の通り、ブラウザからダイレクトアクセスするリソースに関する権限が付与されている。
- IAMロール(unauthenticated):未ログインユーザー用のロール。GenUでは未認証状態のアクセスを想定していないため、今回の構成では実質使われない。下記の通り権限は何も設定されていない。
認証フロー
流れとしては下記になります。
ブラウザから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クレデンシャルは不要です。
まとめ
ここまでで、以下の流れが確認できました。
- クライアントからRoute 53のプライベートホストゾーンで名前解決し、ALB経由でFargate上のコンテナがS3から取得したトップページを返す
- トップページでメールアドレスとパスワードを入力し、ReactアプリからCognito User Poolを呼び出して認証し、トークンを取得する
- ID TokenをCognito Identity Poolに渡し、STS経由で一時クレデンシャルを取得する
- 以降、API GatewayへのリクエストにはID Tokenを、Lambda直接呼び出しやPolly / Transcribeには一時クレデンシャルを使う
次回はバックエンド側の処理を見ていきます。



















