Skip to content

Commit 54bd41d

Browse files
author
Ravi Pranjal
committed
Merge branch 'resDemoAutomateSSO' into 'develop'
Automate sso for res demo stack See merge request mwvaughn/aws-hpc-recipes!98
2 parents 718f8d7 + 671e85c commit 54bd41d

File tree

3 files changed

+733
-79
lines changed

3 files changed

+733
-79
lines changed
Lines changed: 300 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,300 @@
1+
Description: Keycloak Server
2+
3+
Metadata:
4+
AWS::CloudFormation::Interface:
5+
ParameterGroups:
6+
- Label:
7+
default: Keycloak Configuration
8+
Parameters:
9+
- Keypair
10+
- ServiceAccountPasswordSecretArn
11+
- VpcId
12+
- PublicSubnet
13+
- ServiceAccountUserDN
14+
- UsersDN
15+
- LDAPConnectionURI
16+
- CogntioUserPoolId
17+
- EnvironmentBaseURL
18+
- SAMLRedirectUrl
19+
20+
Parameters:
21+
Keypair:
22+
Description: EC2 Keypair to access management instance.
23+
Type: AWS::EC2::KeyPair::KeyName
24+
25+
ServiceAccountPasswordSecretArn:
26+
Type: String
27+
AllowedPattern: ^(?:arn:(?:aws|aws-us-gov|aws-cn):secretsmanager:[a-z0-9-]{1,20}:[0-9]{12}:secret:[A-Za-z0-9-_+=,\.@]{1,128})?$
28+
Description: Directory Service Root (Service Account) Password Secret ARN
29+
30+
VpcId:
31+
Type: AWS::EC2::VPC::Id
32+
AllowedPattern: vpc-[0-9a-f]{17}
33+
ConstraintDescription: VpcId must begin with 'vpc-', only contain letters (a-f) or numbers(0-9) and must be 21 characters in length
34+
35+
PublicSubnet:
36+
Type: AWS::EC2::Subnet::Id
37+
AllowedPattern: subnet-.+
38+
Description: Select a public subnet from the already selected VPC
39+
40+
ServiceAccountUserDN:
41+
Type: String
42+
AllowedPattern: .+
43+
Description: Provide the Distinguished name (DN) of the service account user in the Active Directory
44+
45+
UsersDN:
46+
Type: String
47+
AllowedPattern: .+
48+
Description: Please provide Users Organization Unit in your active directory under which all of your users exist. For example, OU=Users,DC=RES,DC=example,DC=internal
49+
50+
LDAPConnectionURI:
51+
Type: String
52+
AllowedPattern: .+
53+
Description: Please provide the active directory connection URI (e.g. ldap://www.example.com)
54+
55+
CogntioUserPoolId:
56+
Type: String
57+
AllowedPattern: .+
58+
Description: Please provide the Cognito user pool id (e.g. us-east-1_ababab)
59+
60+
EnvironmentBaseURL:
61+
Type: String
62+
AllowedPattern: https?://.+
63+
Description: Please provide your base URL for your environment
64+
65+
SAMLRedirectUrl:
66+
Type: String
67+
AllowedPattern: https://.+\.amazoncognito\.com/saml2/idpresponse
68+
Description: Please provide the SAML redirect URL
69+
70+
Mappings:
71+
Keycloak:
72+
Config:
73+
Version: "24.0.3"
74+
75+
76+
Resources:
77+
KeycloakSecret:
78+
Type: AWS::SecretsManager::Secret
79+
Properties:
80+
Name: !Sub
81+
- KeycloakSecret-${AWS::StackName}-${StackIdSuffix}
82+
- StackIdSuffix: !Select [2, !Split ['/', !Ref 'AWS::StackId']]
83+
Description: Keycloak secret
84+
GenerateSecretString:
85+
PasswordLength: 14
86+
ExcludePunctuation: true
87+
88+
KeycloakEC2InstanceRole:
89+
Type: AWS::IAM::Role
90+
Properties:
91+
AssumeRolePolicyDocument:
92+
Version: 2012-10-17
93+
Statement:
94+
- Effect: Allow
95+
Principal:
96+
Service:
97+
- ec2.amazonaws.com
98+
Action:
99+
- sts:AssumeRole
100+
ManagedPolicyArns:
101+
- !Sub arn:${AWS::Partition}:iam::aws:policy/AmazonSSMManagedInstanceCore
102+
Policies:
103+
- PolicyName: KeycloakEC2InstancePolicy
104+
PolicyDocument:
105+
Version: 2012-10-17
106+
Statement:
107+
- Effect: Allow
108+
Action: secretsmanager:GetSecretValue
109+
Resource:
110+
- !Ref KeycloakSecret
111+
- !Ref ServiceAccountPasswordSecretArn
112+
- Effect: Allow
113+
Action:
114+
- logs:CreateLogGroup
115+
- logs:CreateLogStream
116+
- logs:PutLogEvents
117+
Resource: '*'
118+
119+
KeycloakEC2InstanceProfile:
120+
Type: AWS::IAM::InstanceProfile
121+
Properties:
122+
Roles:
123+
- !Ref KeycloakEC2InstanceRole
124+
125+
KeycloakSecurityGroup:
126+
Type: AWS::EC2::SecurityGroup
127+
Properties:
128+
GroupDescription: Keycloak security group
129+
VpcId: !Ref VpcId
130+
SecurityGroupIngress:
131+
- IpProtocol: tcp
132+
FromPort: 80
133+
ToPort: 80
134+
CidrIp: "0.0.0.0/0"
135+
136+
KeycloakEC2Instance:
137+
Type: AWS::EC2::Instance
138+
DependsOn:
139+
- KeycloakSecurityGroup
140+
- KeycloakEC2InstanceProfile
141+
- KeycloakSecret
142+
CreationPolicy:
143+
ResourceSignal:
144+
Timeout: PT15M
145+
Properties:
146+
ImageId: '{{resolve:ssm:/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-6.1-x86_64:75}}'
147+
InstanceType: t3.micro
148+
KeyName: !Ref Keypair
149+
IamInstanceProfile: !Ref KeycloakEC2InstanceProfile
150+
SubnetId: !Ref PublicSubnet
151+
SecurityGroupIds:
152+
- !Ref KeycloakSecurityGroup
153+
Tags:
154+
- Key: Name
155+
Value: !Sub keycloak-${AWS::StackName}
156+
UserData:
157+
Fn::Base64:
158+
Fn::Sub:
159+
- |
160+
#!/bin/sh -x
161+
mkdir -p /root/bootstrap && cd /root/bootstrap
162+
mkdir -p /root/bootstrap/logs/
163+
exec > /root/bootstrap/logs/userdata.log 2>&1
164+
165+
#Install java17
166+
sudo yum -y install java-17-amazon-corretto-headless
167+
export KEYCLOAK_VERSION=${KeycloakVersion}
168+
wget https://github.com/keycloak/keycloak/releases/download/$KEYCLOAK_VERSION/keycloak-$KEYCLOAK_VERSION.zip
169+
unzip keycloak-$KEYCLOAK_VERSION.zip
170+
171+
cd keycloak-$KEYCLOAK_VERSION
172+
173+
export KC_HTTP_PORT=80
174+
export KEYCLOAK_ADMIN=admin
175+
set +x
176+
export KEYCLOAK_ADMIN_PASSWORD=$(aws secretsmanager get-secret-value --secret-id ${KeycloakSecret} --query SecretString --region ${AWS::Region} --output text)
177+
set -x
178+
179+
# Start Keycloak
180+
sudo -E nohup ./bin/kc.sh start-dev --http-port 80 > keycloak.log &
181+
sleep 30
182+
183+
SERVER_URL="http://0.0.0.0:80"
184+
MAX_ATTEMPTS=15
185+
RETRY_INTERVAL=10
186+
187+
attempt=0
188+
while [ $attempt -lt $MAX_ATTEMPTS ]; do
189+
response=$(curl -s -o /dev/null -w "%{http_code}" "$SERVER_URL")
190+
if [ "$response" == "302" ]; then
191+
echo "Server is up!"
192+
break
193+
else
194+
echo "Server is not yet up. Retrying in $RETRY_INTERVAL seconds..."
195+
sleep $RETRY_INTERVAL
196+
((attempt++))
197+
fi
198+
done
199+
200+
if [ $(curl -s -o /dev/null -w "%{http_code}" "$SERVER_URL") != 302 ]; then
201+
/opt/aws/bin/cfn-signal -e 1 --stack "${AWS::StackName}" --resource "KeycloakEC2Instance" --region "${AWS::Region}"
202+
fi
203+
204+
echo "Keycloak server is up"
205+
# Login to Keycloak
206+
set +x
207+
./bin/kcadm.sh config credentials --server $SERVER_URL --realm master --user admin --password $KEYCLOAK_ADMIN_PASSWORD
208+
set -x
209+
210+
# Create realm named 'res'
211+
./bin/kcadm.sh create realms -s realm=res -s id=res -s enabled=true -o
212+
213+
# Set sslRequired to NONE
214+
./bin/kcadm.sh update realms/master -s sslRequired=NONE --server $SERVER_URL
215+
./bin/kcadm.sh update realms/res -s sslRequired=NONE --server $SERVER_URL
216+
217+
#Configure Keycloak
218+
#Get ServiceAccount passsword
219+
set +x
220+
serviceAccountPassword=$(aws secretsmanager get-secret-value --secret-id ${ServiceAccountPasswordSecretArn} --query SecretString --region ${AWS::Region} --output text)
221+
222+
#Create storage component to sync from AD
223+
componentId=$(./bin/kcadm.sh create components -s name=ldap -s parentId=res -s providerId=ldap -s providerType=org.keycloak.storage.UserStorageProvider \
224+
-s 'config.authType=["simple"]' -s "config.bindCredential=[\"$serviceAccountPassword\"]" -s 'config.bindDn=["${ServiceAccountUserDN}"]' \
225+
-s 'config.connectionUrl=["${LDAPConnectionURI}"]' -s 'config.editMode=["READ_ONLY"]' -s 'config.enabled=["true"]' -s 'config.rdnLDAPAttribute=["cn"]' \
226+
-s 'config.searchScope=["2"]' -s 'config.usernameLDAPAttribute=["sAMAccountName"]' \
227+
-s 'config.usersDn=["${UsersDN}"]' -s 'config.uuidLDAPAttribute=["objectGUID"]' \
228+
-s 'config.vendor=["ad"]' -s 'config.userObjectClasses=["person, organizationalPerson, user"]' -r res -i)
229+
set -x
230+
231+
# Trigger user sync
232+
./bin/kcadm.sh create user-storage/$componentId/sync?action=triggerFullSync -r res
233+
234+
#Create SSO SAML client for SSO
235+
clientId=$(./bin/kcadm.sh create clients -r res -s baseUrl=${EnvironmentBaseURL} \
236+
-s clientId=urn:amazon:cognito:sp:${CogntioUserPoolId} -s name=saml -s protocol=saml -s 'redirectUris=["*"]' -s rootUrl=${EnvironmentBaseURL} \
237+
-s 'attributes.saml_name_id_format=email' -s 'attributes."post.logout.redirect.uris"=${EnvironmentBaseURL}' \
238+
-s 'attributes."saml.client.signature"=false' -s 'attributes."saml.force.post.binding"=true' -s 'attributes."saml.authnstatement"=true' \
239+
-s 'attributes."saml_assertion_consumer_url_post"=${SAMLRedirectUrl}' \
240+
-s 'attributes.saml_single_logout_service_url_redirect=${EnvironmentBaseURL}' -i)
241+
242+
# Create email mapper
243+
./bin/kcadm.sh create clients/$clientId/protocol-mappers/models -s name=email_mapper -s protocol=saml -s protocolMapper=saml-user-property-mapper \
244+
-s 'config."attribute.name"=email' -s 'config."attribute.nameformat"=Unspecified' -s 'config."friendly.name"=email_mapper' -s 'config."user.attribute"=email' -r res
245+
246+
##Schedule crontabs
247+
#Install crontab on al3
248+
sudo yum -y install cronie
249+
sudo systemctl enable crond.service
250+
sudo systemctl start crond.service
251+
252+
#Crontab1 - service account password rotation - script
253+
echo -e "#!/bin/sh -x
254+
exec >> /root/bootstrap/logs/userdata.log 2>&1
255+
echo Updating service account password
256+
cd /root/bootstrap/keycloak-$KEYCLOAK_VERSION
257+
SERVER_URL=\"http://0.0.0.0:80\"
258+
set +x
259+
kc_admin_password=\$(aws secretsmanager get-secret-value --secret-id ${KeycloakSecret} --query SecretString --region ${AWS::Region} --output text)
260+
serviceAccountPassword=\$(aws secretsmanager get-secret-value --secret-id ${ServiceAccountPasswordSecretArn} --query SecretString --region ${AWS::Region} --output text)
261+
./bin/kcadm.sh config credentials --server \$SERVER_URL --realm master --user admin --password \$kc_admin_password
262+
./bin/kcadm.sh update components/$componentId -s name=ldap -s parentId=res -s providerId=ldap -s providerType=org.keycloak.storage.UserStorageProvider \\
263+
-s 'config.authType=[\"simple\"]' -s \"config.bindCredential=[\\\"\$serviceAccountPassword\\\"]\" -s 'config.bindDn=[\"${ServiceAccountUserDN}\"]' \\
264+
-s 'config.connectionUrl=[\"${LDAPConnectionURI}\"]' -s 'config.editMode=[\"READ_ONLY\"]' -s 'config.enabled=[\"true\"]' -s 'config.rdnLDAPAttribute=[\"cn\"]' \\
265+
-s 'config.searchScope=[\"2\"]' -s 'config.usernameLDAPAttribute=[\"sAMAccountName\"]' \\
266+
-s 'config.usersDn=[\"${UsersDN}\"]' -s 'config.uuidLDAPAttribute=[\"objectGUID\"]' \\
267+
-s 'config.vendor=[\"ad\"]' -s 'config.userObjectClasses=[\"person, organizationalPerson, user\"]' -r res
268+
set -x
269+
./bin/kcadm.sh create user-storage/$componentId/sync?action=triggerFullSync -r res
270+
" > /root/bootstrap/password_rotation.sh
271+
chmod +x /root/bootstrap/password_rotation.sh
272+
273+
#Crontab2 - user sync - script
274+
echo -e "#!/bin/sh -x
275+
exec >> /root/bootstrap/logs/userdata.log 2>&1
276+
echo Syncing users
277+
cd /root/bootstrap/keycloak-$KEYCLOAK_VERSION
278+
SERVER_URL=\"http://0.0.0.0:80\"
279+
set +x
280+
kc_admin_password=\$(aws secretsmanager get-secret-value --secret-id ${KeycloakSecret} --query SecretString --region ${AWS::Region} --output text)
281+
./bin/kcadm.sh config credentials --server \$SERVER_URL --realm master --user admin --password \$kc_admin_password
282+
set -x
283+
./bin/kcadm.sh create user-storage/$componentId/sync?action=triggerFullSync -r res
284+
" > /root/bootstrap/user_sync.sh
285+
chmod +x /root/bootstrap/user_sync.sh
286+
287+
(crontab -l; echo "*/30 * * * * /root/bootstrap/password_rotation.sh") | crontab -
288+
(crontab -l; echo "*/5 * * * * /root/bootstrap/user_sync.sh") | crontab -
289+
290+
# Signal stack to continue based on last command output
291+
/opt/aws/bin/cfn-signal -e $? --stack "${AWS::StackName}" --resource "KeycloakEC2Instance" --region "${AWS::Region}"
292+
- KeycloakVersion: !FindInMap [Keycloak, Config, Version]
293+
294+
Outputs:
295+
KeycloakUrl:
296+
Description: Keycloak administrator URL
297+
Value: !Sub http://${KeycloakEC2Instance.PublicIp}:80
298+
KeycloakAdminPasswordSecretArn:
299+
Description: Keycloak password for admin user
300+
Value: !Sub ${KeycloakSecret}

0 commit comments

Comments
 (0)