📝
ALB を Cognito と統合してユーザー認証する機能を CDK で実装してみた
ALB を Amazon Cognito と統合してユーザーを認証する | AWS re:Post
CDK でやってみました。
前提
- CDK 実行環境: Cloud9
- ドメインおよびホストゾーンは Route 53 に登録済み
- ACM で証明書発行済み
- CDK の言語は TypeScript
-
cdk bootstrap
は実行済み
CDK プロジェクト作成
$ mkdir -p ~/.npm-global
$ npm config set prefix '~/.npm-global'
$ echo 'export PATH=~/.npm-global/bin:$PATH' >> ~/.bashrc
$ source ~/.bashrc
$ npm install -g typescript
$ tsc -v
Version 5.7.3
$ mkdir alb-cognito-ec2
$ cd alb-cognito-ec2
$ cdk init app --language typescript
CDK コード
lib/alb-cognito-ec2-stack.ts
import * as cdk from 'aws-cdk-lib';
import { Stack, StackProps, Duration } from 'aws-cdk-lib';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2';
import * as cognito from 'aws-cdk-lib/aws-cognito';
import * as iam from 'aws-cdk-lib/aws-iam';
import * as certificatemanager from 'aws-cdk-lib/aws-certificatemanager';
import * as route53 from 'aws-cdk-lib/aws-route53';
import * as route53targets from 'aws-cdk-lib/aws-route53-targets';
import { Construct } from 'constructs';
import { InstanceTarget } from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets';
import { AuthenticateCognitoAction } from 'aws-cdk-lib/aws-elasticloadbalancingv2-actions';
export class AlbCognitoEc2Stack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
// ドメイン名と証明書ARN
const domainName = 'your-domain';
const certificateArn = 'your-certificateArn;
// 既存の証明書をインポート
const certificate = certificatemanager.Certificate.fromCertificateArn(
this,
'Certificate',
certificateArn
);
// VPC(パブリックサブネットのみ、2つのAZ)
const vpc = new ec2.Vpc(this, 'MyVpc', {
maxAzs: 2, // 2つのAZ
subnetConfiguration: [
{
cidrMask: 24,
name: 'Public',
subnetType: ec2.SubnetType.PUBLIC,
},
],
natGateways: 0, // NATゲートウェイなし
});
// EC2 セキュリティグループ
const ec2SG = new ec2.SecurityGroup(this, 'EC2SG', {
vpc,
allowAllOutbound: true,
});
ec2SG.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP');
// EC2 インスタンス(パブリックサブネットに配置)
const ec2Instance = new ec2.Instance(this, 'WebServer', {
vpc,
instanceType: ec2.InstanceType.of(ec2.InstanceClass.T2, ec2.InstanceSize.MICRO),
machineImage: ec2.MachineImage.latestAmazonLinux2023(),
securityGroup: ec2SG,
vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // パブリックサブネットに明示的に配置
});
// Apache インストールとindex.html作成のユーザーデータ
ec2Instance.addUserData(
'sudo yum update -y',
'sudo yum install -y httpd',
'sudo systemctl enable httpd',
'sudo systemctl start httpd',
// index.htmlを作成
'sudo cat > /var/www/html/index.html << EOF',
'<!DOCTYPE html>',
'<html lang="ja">',
'<head>',
' <meta charset="UTF-8">',
' <meta name="viewport" content="width=device-width, initial-scale=1.0">',
' <title>ログイン成功</title>',
' <style>',
' body {',
' font-family: Arial, sans-serif;',
' display: flex;',
' justify-content: center;',
' align-items: center;',
' height: 100vh;',
' margin: 0;',
' background-color: #f0f8ff;',
' }',
' .container {',
' text-align: center;',
' padding: 40px;',
' background-color: white;',
' border-radius: 10px;',
' box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);',
' }',
' h1 {',
' color: #28a745;',
' margin-bottom: 20px;',
' }',
' p {',
' color: #666;',
' font-size: 18px;',
' }',
' </style>',
'</head>',
'<body>',
' <div class="container">',
' <h1>🎉 ログイン成功!</h1>',
' <p>Cognito認証が正常に完了しました。</p>',
' <p>ALB + Cognito + EC2の連携テストが成功しています。</p>',
' </div>',
'</body>',
'</html>',
'EOF',
// Apacheの権限設定
'sudo chown apache:apache /var/www/html/index.html',
'sudo chmod 644 /var/www/html/index.html',
// Apacheを再起動
'sudo systemctl restart httpd'
);
// ALB セキュリティグループ
const albSG = new ec2.SecurityGroup(this, 'AlbSG', {
vpc,
allowAllOutbound: true,
});
albSG.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP');
albSG.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS');
// ALB(2つのパブリックサブネットに配置)
const alb = new elbv2.ApplicationLoadBalancer(this, 'MyALB', {
vpc,
internetFacing: true,
securityGroup: albSG,
loadBalancerName: 'alb-cognito-demo',
vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC },
});
// Route 53 ホストゾーンの取得(既存のものを参照)
const hostedZone = route53.HostedZone.fromLookup(this, 'HostedZone', {
domainName: domainName,
});
// ALB用のALIASレコードを作成
new route53.ARecord(this, 'AliasRecord', {
zone: hostedZone,
recordName: domainName,
target: route53.RecordTarget.fromAlias(new route53targets.LoadBalancerTarget(alb)),
});
// HTTPSリスナー
const httpsListener = alb.addListener('HttpsListener', {
port: 443,
certificates: [certificate],
open: true,
});
// HTTPからHTTPSへのリダイレクト
const httpListener = alb.addListener('HttpListener', {
port: 80,
open: true,
});
httpListener.addAction('RedirectToHttps', {
action: elbv2.ListenerAction.redirect({
protocol: 'HTTPS',
port: '443',
permanent: true,
}),
});
// Cognito User Pool
const userPool = new cognito.UserPool(this, 'MyUserPool', {
selfSignUpEnabled: true,
signInAliases: { email: true },
autoVerify: { email: true },
});
// User Pool Client
const userPoolClient = new cognito.UserPoolClient(this, 'MyUserPoolClient', {
userPool,
generateSecret: true,
oAuth: {
flows: {
authorizationCodeGrant: true,
},
scopes: [
cognito.OAuthScope.EMAIL,
cognito.OAuthScope.OPENID,
cognito.OAuthScope.PROFILE,
],
callbackUrls: [
`https://${domainName}/oauth2/idpresponse`,
],
logoutUrls: [
`https://${domainName}/oauth2/idpresponse`,
],
},
authFlows: {
userPassword: true,
userSrp: true,
},
supportedIdentityProviders: [
cognito.UserPoolClientIdentityProvider.COGNITO,
],
});
// User Pool Domain
const userPoolDomain = new cognito.UserPoolDomain(this, 'MyUserPoolDomain', {
userPool,
cognitoDomain: {
domainPrefix: `alb-demo-${this.account}`,
},
});
// Target Group
const targetGroup = new elbv2.ApplicationTargetGroup(this, 'TargetGroup', {
vpc,
port: 80,
protocol: elbv2.ApplicationProtocol.HTTP,
targets: [new InstanceTarget(ec2Instance)],
healthCheck: {
path: '/index.html',
interval: Duration.seconds(30),
timeout: Duration.seconds(5),
healthyThresholdCount: 2,
unhealthyThresholdCount: 3,
protocol: elbv2.Protocol.HTTP,
port: '80',
healthyHttpCodes: '200',
},
});
// HTTPS リスナーに Cognito 認証アクションを追加
httpsListener.addAction('CognitoAuth', {
action: new AuthenticateCognitoAction({
userPool,
userPoolClient,
userPoolDomain,
next: elbv2.ListenerAction.forward([targetGroup]),
}),
});
// 出力
new cdk.CfnOutput(this, 'LoadBalancerDNS', {
value: alb.loadBalancerDnsName,
description: 'Load Balancer DNS Name',
});
new cdk.CfnOutput(this, 'AccessURL', {
value: `https://${domainName}`,
description: 'Access URL with HTTPS',
});
new cdk.CfnOutput(this, 'HostedZoneId', {
value: hostedZone.hostedZoneId,
description: 'Route 53 Hosted Zone ID',
});
}
}
bin/alb-cognito-ec2
#!/usr/bin/env node
import * as cdk from 'aws-cdk-lib';
import { AlbCognitoEc2Stack } from '../lib/alb-cognito-ec2-stack';
const app = new cdk.App();
new AlbCognitoEc2Stack(app, 'AlbCognitoEc2Stack', {
env: {
account: 'your-account-id',
region: 'ap-northeast-1',
},
});
コードの概要は以下の通りです。
- パブリックサブネットを 2 つ作成
- EC2 インスタンスを 1 つ作成
- ユーザーデータで index.html を作成
- ALB を作成
- ターゲットは EC2 インスタンス
- Cognito による認証を追加
- Cognito ユーザープールを作成
- 認証が成功したら ALB 経由で EC2 インスタンスにアクセス
デプロイ
$ cdk synth
$ cdk deploy
デプロイ後、CloudFormation スタックの出力から AccessURL のドメインにアクセスします。
ログイン画面からサインアップします。
認証コードを入力します。
以下の画面が表示されれば成功です。
まとめ
今回は ALB を Cognito と統合してユーザー認証する機能を CDK で実装してみました。
どなたかの参考になれば幸いです。
Discussion