This article assumes that the user backend for Keycloak is FreeIPA. Regardless of that the instructions will apply to any other setup with minor modifications. Here we use two different AWS accounts renamed to 123412341234 and 567856785678 to protect the personal information of the innocent. The Keycloak staging cluster on which this integration was done is called id-staging.example.org and the production cluster is called id.example.org. The Keycloak realm is EXAMPLE.ORG. Replace those with the values correct for your environment.
But let's get on with the actual integration.
- Create a new SAML client in Keycloak. This client will be used for all AWS IAM accounts and their roles.
The ClientId should really be “urn:amazon:webservices”, as this is the audience restriction in the user claim. This is technically not necessary, however. But not using it is kind of hackish, and may make thing difficult in the future.
Enter the IDP Initiated SSO URL as “aws”, or a name to your liking. This is an important setting as AWS does not support Service Provider initiated logins, only IdP initiated logins. So your goto place for all things AWS SAML: is
https://id-staging.example.org/auth/realms/EXAMPLE.ORG/protocol/saml/clients/aws
- Ensure there are no roles in the Roles tab.
- Ensure there is nothing in Assigned Default Client Scopes in the Client Scopes tab
- Ensure there are no Mappers defined in the Mappers tab
- Ensure Full Scope Allowe is Off in the Scope tab
2. Create a new client scope aws_roles. This will include all AWS roles that AWS requires, as well as mappers to give them the proper format. We do this formatting, because newest Keycloaks do not allow semicolons in role names. They do allow renaming the roles. We do this renaming with the help of Role Name Mappers. For every role (that in this case comes from the equivalent FreeIPA group), there needs to be a mapper.
3. Create the mappers for three required attributes. If AWS finds anything else than these, or errors in these (like a role in wrong format), you will get an invalid response message, and things will not work. These must be only these attributes and they must be formatted correctly in the response.
3.1 Session Role. The AWS attribute is https://aws.amazon.com/SAML/Attributes/Role. This will create a list of all the roles the users is permitted to assume:
3.2 Session Name. The SAML attribute is https://aws.amazon.com/SAML/Attributes/RoleSessionName. This will create the username for AWS:
3.3 Session Duration. The SAML attribute is https://aws.amazon.com/SAML/Attributes/SessionDuration. This will define the maximum time in seconds, that the user session is allowed to persist.
4. Create the FreeIPA group for the specific AWS IAM role in a specific AWS account. Do this initially based on current AWS account groups and their members. We recommend giving it a name “aws-<account>-<role>”, here “aws-qa-administrators”. Add the users, who will need to be able to assume this role, as members. Do this for every required AWS IAM role. These will become realm level roles in Keycloak:
5. Map the FreeIPA group to Keycloak realm level role in Keycloak, here “aws-qa-administrators”. Navigate to Groups → aws-qa-administrators → Role Mapping and add selected role “aws-qa-administrators” to Assigned Roles. Do this for every required AWS IAM role for which you created an FreeIPA group.
You can select Member tab and ensure you have the correct people:
You can also check Users → <user> → Role Mappings → Effective Roles if a certain user can correctly assume the roles he/she needs:
6. Go to Clients → <clientname> → Scope and assign all the realm roles prefixed with aws- to the the client:
Now test with Chrome SAML panel extension and verify that your claims are correct, you can choose your role and login by using:
https://id-staging.example.org/auth/realms/EXAMPLE.ORG/protocol/saml/clients/aws
When these are imported in production, you of course need to go to:
https://id.example.org/auth/realms/EXAMPLE.ORG/protocol/saml/clients/aws
Notice how there are different roles in different AWS accounts:
saml:AttributeStatement
<saml:Attribute
FriendlyName="Session Name"
Name="https://aws.amazon.com/SAML/Attributes/RoleSessionName"
NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
<saml:AttributeValue
xmlns:xs="http://www.w3.org/2001/XMLSchema%22
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance%22
xsi:type="xs:string">
john.doe
</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute
FriendlyName="Session Duration"
Name="https://aws.amazon.com/SAML/Attributes/SessionDuration"
NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
<saml:AttributeValue
xmlns:xs="http://www.w3.org/2001/XMLSchema%22
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance%22
xsi:type="xs:string">
28800
</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute
FriendlyName="Session Role"
Name="https://aws.amazon.com/SAML/Attributes/Role"
NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
<saml:AttributeValue
xmlns:xs="http://www.w3.org/2001/XMLSchema%22
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance%22
xsi:type="xs:string">
arn:aws:iam::123412341234:role/SAMLTestGroup,
arn:aws:iam::123412341234:saml-provider/id-staging.example.org
</saml:AttributeValue>
<saml:AttributeValue
xmlns:xs="http://www.w3.org/2001/XMLSchema%22
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance%22
xsi:type="xs:string">
arn:aws:iam::567856785678:role/SAMLAdministrator,
arn:aws:iam::567856785678:saml-provider/id-staging.example.org
</saml:AttributeValue>
<saml:AttributeValue
xmlns:xs="http://www.w3.org/2001/XMLSchema%22
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance%22
xsi:type="xs:string">
arn:aws:iam::123412341234:role/SAMLSysadmin,
arn:aws:iam::123412341234:saml-provider/id-staging.example.org
</saml:AttributeValue>
<saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema%22
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance%22
xsi:type="xs:string">
arn:aws:iam::123412341234:role/SAMLDeveloper,
arn:aws:iam::123412341234:saml-provider/id-staging.example.org
</saml:AttributeValue>
</saml:Attribute>
</saml:AttributeStatement>