KeycloakをALB+EC2構成で構築してみた
先日、Keycloakの環境を検証する機会があったので、構築手順をまとめました。
本ブログはその備忘録です。
Keycloakとは
KeycloakはOIDC認証等を利用してシングル・サインオンを実現するOSSです。
より詳細に知りたい方は、@ITの記事がわかりやすいので、こちらを御覧ください。
構成図
こんな感じの構成をCloudFormationで作ります。Keycloakは、EC2上のDockerで動かします。
HTTPS接続できるようにするため、ALBをSSL終端にしたいです。 証明書にはACMを利用します。 ACMを作成する場合、DNSにRoute53を利用した方が楽なので、Route53も利用します。
Route53のホストゾーンとACMだけは、マネジメントコンソールから作ります。
構築手順
Route53ホストゾーンの構築
Route53を利用するには、何かしらのドメインを所有している必要があります。 弊社ブログを参考に、無料のドメインを利用してみるのもよいと思います。
作成したホストゾーンのドメイン名はメモしておいてください。のちほどCloudFormationテンプレートで使用します。
ACMの構築
ALBで利用するためのACMを作成します。
ACM構築の詳細は、弊社ブログを御覧ください。
作成したACMのARNはメモしておいてください。のちほどCloudFormationテンプレートで使用します。
CloudFormationでALB+EC2の構築
CloudFormationで残りのAWSリソースを作成します。
以下のボタンをクリックすると、CloudFormationスタックのクイック作成ができます。
作成前にパラメーターとして、以下3つの項目を入力してください。
- HostedZoneName: Route53で登録したドメイン名。末尾のピリオドは省いてください。 (例:example.com)
- CertificateArn: 作成したACMのARN。 (例:arn:aws:acm:ap-northeast-1:123456789012:certificate/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
- AllowedCidr: ALBに対して接続を許可するネットワークCIDR。 (例:192.0.2.2/32)
パラメーターを入力したら、一番下までスクロールしてIAMのチェックを入れてスタックを作成します。
CloudFormationのスタックが作成できたら、AWSリソースの構築は完了です。
セッションマネージャーでDockerを動かす
EC2の構築ができたので、KeycloakをDockerで動かします。こちらのDockerイメージを利用します。
Dockerを動かすためにEC2へ接続する必要がありますが、今回はセッションマネージャーを利用します。 セッションマネージャーはざっくりいうと、ブラウザ上のシェルでEC2のCLI操作ができる機能です。詳細は弊社ブログを御覧ください。
まずはマネジメントコンソールで、Systems Managerのセッションマネージャーを開いて、セッションの開始をクリックします。
そうすると、接続可能なインスタンスが表示されるので、接続したいインスタンスを選択して、セッションを開始します。
そうすると、ブラウザでEC2インスタンスのシェルを動かすことができます。SSHクライアントいらずです。
セッションマネージャーを利用してEC2へ接続する場合、ログインユーザーは ssm-user
になります。ですので、ssm-user
ユーザーでDockerが使えるようにコマンドを実行して設定します。
$ sudo usermod -a -G docker ssm-user
コマンドを実行したら、設定を反映させるためにセッションを張り直す必要があります。 1回終了して、もう一度セッションマネージャーで新しいセッションを開始します。
新しいセッションが開始できたら、次のコマンドのパスワードを適当に変更して、Dockerでkeycloakを動かします。
$ PASSWORD="Password@01" $ docker run -d \ -p 8080:8080 \ -e PROXY_ADDRESS_FORWARDING=true \ -e KEYCLOAK_USER=admin \ -e KEYCLOAK_PASSWORD=${PASSWORD} \ jboss/keycloak:7.0.0
しばらくすると、Dockerが起動してKeycloakにログインできるようになります。
Keycloakにログインする
Route53でALBに対して、keycloak. + ドメイン名のエイリアスを作成しているので、そのURLでアクセスすることが可能です。 (ドメイン名がexample.comなら、https://keycloak.example.com/でアクセスできます。)
Docker実行時に設定したパスワードでログインすることができます。
これでKeycloakの検証環境を構築することができました。
終わりに
ALB+EC2(Docker)を使って、HTTPS接続できるKeycloakの検証環境を構築してみました。 今回はKeycloak環境を構築しましたが、セッションマネージャーで実行していたDockerイメージを変えれば別の環境にさっとすげ替えることもできます。 Dockerは検証環境の構築にも便利です。
Keycloakの方は、Cognitoとの連携あたりを検証したいと思います。
【おまけ】CloudFormationテンプレート
AWSTemplateFormatVersion: '2010-09-09' Description: "keycloak sample" Metadata: AWS::CloudFormation::Interface: ParameterGroups: - Label: default: Required Parameters: - HostedZoneName - CertificateArn - AllowedCidr - Label: default: Options Parameters: - CidrPrefix - SsmParameterValueawsserviceamiamazonlinuxlatestamzn2 - TargetGroupPort - HostName - InstanceType Parameters: HostedZoneName: Type: String Description: "(Example: example.com)" CertificateArn: Type: String Description: "(Example: arn:aws:acm:ap-northeast-1:123456789012:certificate/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)" AllowedCidr: Type: String Description: "(Example: 192.0.2.2/32)" AllowedPattern: '^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))$' CidrPrefix: Type: String Default: 10.35 SsmParameterValueawsserviceamiamazonlinuxlatestamzn2: Type: AWS::SSM::Parameter::Value<String> Default: /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2 TargetGroupPort: Type: String Default: 8080 HostName: Type: String Default: keycloak InstanceType: Type: String Default: t3.small Resources: Vpc: Type: AWS::EC2::VPC Properties: CidrBlock: !Sub "${CidrPrefix}.0.0/16" EnableDnsSupport: true EnableDnsHostnames: true Tags: - Key: Name Value: !Sub "${AWS::StackName}-vpc" Igw: Type: AWS::EC2::InternetGateway Properties: Tags: - Key: Name Value: !Sub "${AWS::StackName}-igw" IgwAttachment: Type: AWS::EC2::VPCGatewayAttachment Properties: InternetGatewayId: !Ref Igw VpcId: !Ref Vpc RouteDefault: Type: AWS::EC2::Route DependsOn: IgwAttachment Properties: RouteTableId: !Ref RouteTable DestinationCidrBlock: 0.0.0.0/0 GatewayId: !Ref Igw RouteTable: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref Vpc Tags: - Key: Name Value: !Sub "${AWS::StackName}-route-table" SubnetFront0: Type: AWS::EC2::Subnet Properties: AvailabilityZone: !Select [ 0, !GetAZs '' ] CidrBlock: !Sub "${CidrPrefix}.0.0/24" MapPublicIpOnLaunch: true Tags: - Key: Name Value: !Sub "${AWS::StackName}-front-0-subnet" VpcId: !Ref Vpc SubnetFront1: Type: AWS::EC2::Subnet Properties: AvailabilityZone: !Select [ 1, !GetAZs '' ] CidrBlock: !Sub "${CidrPrefix}.1.0/24" MapPublicIpOnLaunch: true Tags: - Key: Name Value: !Sub "${AWS::StackName}-front-1-subnet" VpcId: !Ref Vpc SubnetRouteTableAttachmentFront0: Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: !Ref RouteTable SubnetId: !Ref SubnetFront0 SubnetRouteTableAttachmentFront1: Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: !Ref RouteTable SubnetId: !Ref SubnetFront1 # Security Group for ALB SecurityGroupAlb: Type: AWS::EC2::SecurityGroup Properties: GroupName: !Sub "${AWS::StackName}-alb-sg" GroupDescription: for alb SecurityGroupIngress: - IpProtocol: tcp FromPort: 443 ToPort: 443 CidrIp: !Ref AllowedCidr VpcId: !Ref Vpc Tags: - Key: Name Value: !Sub "${AWS::StackName}-alb-sg" # Security Group for Keycloak SecurityGroupKeycloak: Type: AWS::EC2::SecurityGroup Properties: GroupName: !Sub "${AWS::StackName}-keycloak-sg" GroupDescription: for keycloak server SecurityGroupIngress: - SourceSecurityGroupId: !Ref SecurityGroupAlb IpProtocol: tcp FromPort: !Ref TargetGroupPort ToPort: !Ref TargetGroupPort VpcId: !Ref Vpc Tags: - Key: Name Value: !Sub "${AWS::StackName}-keycloak-sg" # ALB ApplicationLoadBalancer: Type: AWS::ElasticLoadBalancingV2::LoadBalancer Properties: Name: !Sub "${AWS::StackName}-alb" Type: application Scheme: internet-facing IpAddressType: ipv4 SecurityGroups: - !Ref SecurityGroupAlb Subnets: - !Ref SubnetFront0 - !Ref SubnetFront1 Tags: - Key: Name Value: !Sub "${AWS::StackName}-alb" ApplicationLoadBalancerListenerHTTPS: Type: AWS::ElasticLoadBalancingV2::Listener Properties: Certificates: - CertificateArn: !Ref CertificateArn Port: 443 Protocol: HTTPS DefaultActions: - Type: forward TargetGroupArn: !Ref AlbTargetGroupKeycloak LoadBalancerArn: !Ref ApplicationLoadBalancer # ALB Target group AlbTargetGroupKeycloak: Type: AWS::ElasticLoadBalancingV2::TargetGroup Properties: Name: !Sub "${AWS::StackName}-keycloak-tg" Port: !Ref TargetGroupPort Protocol: HTTP Targets: - Id: !Ref KeycloakEc2Instance # Health check HealthCheckProtocol: HTTP HealthyThresholdCount: 3 HealthCheckIntervalSeconds: 30 Matcher: HttpCode: '200' VpcId: !Ref Vpc Tags: - Key: Name Value: !Sub "${AWS::StackName}-keycloak-tg" # IAM Role for EC2 Ec2InstanceRoleForSSM: Type: AWS::IAM::Role Properties: RoleName: !Sub "${AWS::StackName}-ec2-role" AssumeRolePolicyDocument: { "Version": "2012-10-17", "Statement": [ { "Action": "sts:AssumeRole", "Principal": { "Service": "ec2.amazonaws.com" }, "Effect": "Allow", "Sid": "" } ] } ManagedPolicyArns: - 'arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore' InstanceProfileForSSM: Type: AWS::IAM::InstanceProfile Properties: Roles: - !Ref Ec2InstanceRoleForSSM InstanceProfileName: !Sub "${AWS::StackName}-instance-profile" KeycloakEc2Instance: Type: AWS::EC2::Instance Properties: CreditSpecification: CPUCredits: standard IamInstanceProfile: !Ref InstanceProfileForSSM ImageId: !Ref SsmParameterValueawsserviceamiamazonlinuxlatestamzn2 InstanceType: !Ref InstanceType SecurityGroupIds: - !Ref SecurityGroupKeycloak SubnetId: !Ref SubnetFront0 UserData: Fn::Base64: | #!/bin/sh -ex yum update -y yum install docker -y service docker start systemctl enable docker.service usermod -a -G docker ec2-user Tags: - Key: Name Value: !Sub "${AWS::StackName}-keycloak" Route53RecordSetKeycloak: Type: AWS::Route53::RecordSet Properties: AliasTarget: DNSName: !Sub - dualstack.${DNSName} - DNSName: !GetAtt ApplicationLoadBalancer.DNSName HostedZoneId: !GetAtt ApplicationLoadBalancer.CanonicalHostedZoneID HostedZoneName: !Sub - "${HostedZoneName}." - { HostedZoneName: !Ref HostedZoneName } Name: !Sub - "${HostName}.${HostedZoneName}." - { HostName: !Ref HostName, HostedZoneName: !Ref HostedZoneName } Type: A