この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
先日、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