As Keystone progressed with new features and functionality, one of the most sought-after features was support for federated identity: giving administrators the ability to leverage their existing federated identity solution, allowing users to use their own credentials and to leverage existing single sign-on capabilities. One of the key benefits of this approach is that users can exist in an identity store that is not owned or accessible by Keystone, reducing the logic of maintaining user credentials, or supporting a single type of corporate backend (LDAP and not AD). It also allows Keystone to concentrate on authorization and how to better serve OpenStack services.
A brief overview of the terminology used in this chapter:
A trusted provider of identity information. This may be backed by LDAP, SQL, AD, or Mongo. The identity provider is able to construct user attributes into Assertions or Claims.
A service that consumes identity information. In our case, Keystone will be the service provider. Many web applications that utilize single sign-on are examples of service providers.
An XML-based standard approach to federated identity. The resultant document that contains user attributes is called an Assertion.
A newer standard approach to federated identity. Leverages OAuth 2.0, and ditches XML in favor of JSON. The resultant information about the user is called a Claim.
A standard method of representing information and attributes about a user. An identity provider may issue Assertions or Claims, based on the standard they use, for service providers to consume.
In this section we discuss how existing technology can be leveraged and Keystone-specific resources that need to be created.
Keystone’s approach to Federation was based on its decision to run Keystone under the Apache HTTP Server (httpd). Since Apache (and other HTTP servers) is the recommended way to run Keystone, it made sense to architect a solution that allowed Keystone to leverage existing Apache plugins and modules that handle different federation standards for it. For instance, if you intend to use a SAML identity provider, then mod_shib
or mod_auth_mellon
are available plugins; if you intend to use an OpenID Connect identity provider, then mod_auth_openidc
is available. This approach makes Keystone a service provider that is protocol agnostic, since the web server plugins will handle the logic of interacting with the identity provider. The result of a correct setup is that once a user successfully authenticates with their identity provider, Keystone is able to see user attributes as HTTP request environment variables.
One of the difficulties with this approach, and so many of the federated identity standards, is that they are primarily designed to be used in an Internet browser. The reason for this is that any form of authentication can be used, password, two-factor, certificate, etc., so long as the browser can support it and handle the inherent redirects that come with browser-based authentication, then all is good. The issue with using this approach for OpenStack is that so many users of OpenStack interact with their cloud through a command line interface (CLI). Within the standards, there are various levels of support for a non-browser-based flow, but these are often not as user friendly, and may even be disabled or unsupported by the identity provider.
In order to have a full federated identity solution, the Keystone team needed to create a new set of APIs, and resources stored in their own backends. These APIs are intended to allow cloud administrators to add trusted identity providers, specify which protocols the identity providers will support, and create mappings to allow translating user attributes to keystone concepts that model the external identity providers and protocols they will be consuming.
Keystone now has an Identity Provider resource, which is served through the following API: /OS-FEDERATION/identity_providers
. This endpoint allows a cloud administrator to create, delete, update, retrieve, and list identity providers. Each entry is meant to map to an external identity provider that will be trusted to send assertions or claims about an external user. Note that identity provider names are guaranteed to be globally unique in an OpenStack deployment.
Keystone now has a Protocol resource, which is served at the following API: /OS-FEDERATION/identity_providers/{idp_id}/protocols
. This endpoint allows a cloud administrator to create, delete, update, retrieve, and list protocols per identity provider. A protocol has only a name and reference to the mapping that will be used (more on that later). The reasoning behind setting the protocol per identity provider is that an external identity provider may support more than one type of federated authentication, so we must allow an identity provider to have multiple protocols as well. The protocols are meant to mirror the federated identity standard that is being used. Common values for protocol names are saml2
(for SAML) and oidc
(for OpenID Connect).
Mappings are the lynchpin to federated identity in Keystone. A mapping resource has now been created and served at the following API: /OS-FEDERATION/mappings
. This endpoint allows a cloud administrator to create, delete, update, retrieve, and list mappings. A mapping is specified when creating a protocol that an identity provider supports. The actual contents of a mapping is a set of rules that will be evaluated when a Claim or Assertion is seen by Keystone. These rules will vary greatly between deployments, and depend on the attributes issued by the identity provider and how much access the cloud administrator wants to give to federated users.
Now that identity providers, protocols, and mappings have been briefly covered, in this section we discuss what assertions about the incoming user look like, how the mapping engine evaluates these assertions, and the rules used by the mapping engine. Let’s start by showing an OpenID Connect Claim and a SAML assertion, and how each will have its content transformed into HTTP headers by the web server plugins.
An OpenID Connect claim represents information about a user in JSON. There are a number of required fields and values, such as the issuer, the subject, when the claim was issued, and when it will expire. The following is an example OpenID Connect claim after a user has authenticated with an OpenID Connect Identity Provider.
{ "nonce": "9c2b51b0ef3849828d6fedc8019eae2b", "sub": "[email protected]", "at_hash": "RC3QNVOT1h31ofNyFlQskQ", "iss": "https://localhost:8020/oidc/v10/providers/OP", "uniqueSecurityName": "[email protected],c=ca...es,o=ibm.com", "realmName": "w3", "groupIds": [ "cn=SWG_Canada,ou=memberlist,ou=ibmgroups,o=ibm.com", "cn=Toronto_Lab_VPN,ou=memberlist,ou=ibmgroups,o=ibm.com", "cn=IBM Regular Employees Canada,ou=memberlist,ou=ibmgroups,o=ibm.com" ], "exp": 1395358515, "iat": 1395354915, "aud": "rp" }
Looking at the claim above, we can see various fields specific to the authenticated user and the identity provider. The Issuer (iss
) represents the Identity Provider that issued the claim, the Subject (sub
) represents the user that authenticated, and most importantly we have the groupIds
. Note that sub
and iss
(and nonce
, exp
, iat
, and aud
) are required to exist, as per the OpenID Connect specification. The specification also allows for identity providers to add extra fields; in this case, our Identity Provider was backed by our internal bluepages LDAP, and added groupIds
; this field represents a list of group CNs the user is a member of. How does this look to Keystone?
HTTP_OIDC_ISS="https://localhost:8020/oidc/v10/providers/OP" HTTP_OIDC_SUB=”[email protected]” HTTP_GROUP_IDS="cn=SWG_Canada,ou=memberlist,ou=ibmgroups,o=ibm.com; cn=Toronto_Lab_VPN,ou=memberlist,ou=ibmgroups,o=ibm.com; cn=IBM Regular Employees Canada,ou=memberlist,ou=ibmgroups, o=ibm.com"
The above is a representation of how the claims are seen by Keystone. Each claim will be transformed into an HTTP header and placed in the request back to Keystone. Most of these are straight translations, but in the case of a list of groups, it is common to see either commas or semicolons used to separate the items in the list.
A SAML assertion is an XML representation of an authenticated user. Similar to OpenID Connect, there are a number of required fields pertaining to the issuer, the subject, their authorization on the identity provider, and when the assertion was made. The following is a significantly simplified example SAML assertion that was generated after a user has authenticated with an SAML Identity Provider.
<saml2:Assertion IssueInstant="2014-06-11T13:05:28.891Z"> <saml2:Issuer> https://bluepages.ibm.com </saml2:Issuer> <saml:AttributeStatement> <saml2:Attribute Name="UserName"> <saml2:AttributeValue>[email protected]</saml2:AttributeValue> </saml2:Attribute> <saml:Attribute Name="Role"> <saml:AttributeValue>IBM Regular Employ...anada</saml:AttributeValue> </saml:Attribute> <saml:Attribute Name="Role"> <saml:AttributeValue>SWG Canada</saml:AttributeValue> </saml:Attribute> <saml:Attribute Name="Role"> <saml:AttributeValue>Toronto Lab VPN</saml:AttributeValue> </saml:Attribute> </saml:AttributeStatement> </saml2:Assertion>
Looking at the assertion above, we can see various fields specific to the authenticated user and identity provider. The main portion of the assertion we are interested in are the Attributes
in the AttributeStatement
section. This indicates the level of authorization the user has on that identity provider. These field values will be used to construct an effective mapping. How does this look to Keystone?
HTTP_ISSUSER="https://bluepages.ibm.com" HTTP_USERNAME=”[email protected]” HTTP_ROLE="IBM Regular Employees Canada;SWG Canada;Toronto Lab VPN"
Similar to the example above, the information that is returned to Keystone should have a very similar structure when SAML is used as opposed to OpenID Connect; this demonstrates a Keystone protocol-agnostic approach to federating identities.
Another detail about Keystone’s approach to federation is that users coming from identity providers are ephemeral, meaning they will not exist or be stored anywhere on a Keystone database. This decision was made because, if users were to be added or removed, then the databases would need to be updated accordingly, rendering our goal (to eliminate user management in Keystone) moot. The impact of this decision is that the individual ephemeral users will not have a sense of authorization on the cloud, since we would not be able to assign the user a role or a project. To address this issue, mappings were created to convey a sense of where these external entities belong in a Keystone cloud.
The goal of a mapping is to give the ephemeral user just enough authorization in the cloud to perform a task, and also to ensure we have enough data about the user to populate token details, and create auditable information. To accomplish this task, the Keystone team decided to leverage User Groups that are already present in the Identity backend, since groups may have role assignments. Using the information in the claim or assertion, a Keystone administrator should be able to create a mapping that will give the ephemeral user access to a specific part of the cloud.
The overall concept of the input and output of the mapping rules can be seen below.
On the left-hand side of the figure are the HTTP request environment variables, as seen by Keystone; additional examples of these environment variables can be seen in the previous section. Using the mapping rules associated with a specific identity provider and protocol, we are able to determine suitable groups to give the user membership to, and also determine a suitable username and user ID to populate the resulting token and any auditable events.
In this section, we will discuss how the user attributes, represented as HTTP requests headers, can be translated into Keystone concepts. To accomplish this, we select a mapping that contains a set of rules that will be invoked when requests come from a specific identity provider.
So let’s say we are an administrator at Acme Inc., and we want to use cloud resources at IBM. When authenticated with our external identity provider, the following user attributes are returned, represented by HTTP headers:
HTTP_ISSUSER="https://acmepages.acme.com" HTTP_USERNAME=”topol” HTTP_FULL_NAME=”Brad Topol” HTTP_EMAIL=”[email protected]” HTTP_ROLE="Acme IT Dept; IT Administrators; Regular Employees; Non-Contractors"
So, when the cloud administrator at IBM is creating a mapping, what should it look like for users authenticating from Acme Inc.? The answer depends on just how much access the cloud administrator wants to give! Mapping rules are made up of a local
and remote
section; the local
section represents the Keystone resource the users will be mapped to, and the remote
section represents the user attributes in the HTTP header. Below is a sample mapping that can be used to federate user identities from Acme to Keystone.
{ "rules": [ { "local": [ "user": { "name": "{0}" } ], "remote": [ { "type": "HTTP_USERNAME" } ] }, { "local": [ { "group": { "name": "acme_federated_users", "domain": { "name": "acme_private_cloud" } } } ], "remote": [ { "type": "HTTP_ROLE", "any_one_of": [ "Acme IT Dept", "IT Administrators" ] } ] } ] }
Different mapping rules can be used to accomplish the same goal. Below is another example of how we can federate user identities from Acme to Keystone.
{ "rules": [ { "local": [ { "user": { "name": "{0}" }, "groups": { "name": "{1}", "domain": { "id": "0cd5e9" } } } ], "remote": [ { "type": "HTTP_USERNAME" }, { "type": "HTTP_ROLES", "blacklist": [ "Regular Employees”, “Contractors" ] } ] } ] }
Assuming Keystone receives the user attributes in HTTP headers (like the example above), what would happen if the first mapping was used? In this case, a Keystone token will be generated with Brad’s username (topol); if no ID is specified, then it’ll use a URL-safe version of the username. Furthermore, the Keystone token will include a list of groups Brad is successfully matched with, which includes the group ID for the Keystone group “acme_federated_users
” in the domain “acme_private_cloud
”. Brad matched the criteria for mapping into that group since he has any of the roles: “Acme IT Dept” and “IT Administrator.” Note that the term any_one_of is important here, since it means that as long as a single match occurs, the user has met the criteria.
In the second example, we reuse the same rule for choosing the user’s name and ID. The changes are in how the groups are processed. In this example, the local section attempts to use a direct lookup (using the {0}
value initiates a direct lookup) for all incoming values in the HTTP_ROLES list. The only catch is that the group names listed in the blacklist are not looked up.
Now that we’ve got all the background, what does the full authentication flow look like? We will go through all the steps that are performed in a federated authentication request in this section. Note that to initiate an authentication request, the Apache module used must protect a specific path. We recommend the path be in the following style:
GET/POST /OS-FEDERATION/identity_providers/{identity_provider} /protocols/{protocol}/auth
This allows each identity provider to have a separate authentication flow per supported protocol, and gives Keystone enough information to find a mapping to be used for the combination.
Federated authentication flow:
mod_shib
in this case) to redirect the user to authenticate with the external identity provider (through a web page or otherwise).One of the most demanded features in OpenStack was to support single sign-on access. Configuring Keystone for federating identities does not imply that Keystone will have single sign-on support automatically. This is problematic because Horizon maintains the “login” page for OpenStack and runs as a separate process. To solve this issue, the Keystone and Horizon teams worked together to create a solution whereby Keystone and Horizon are able to communicate and trust each other. The result is that end users will see a familiar login page, which is important for branding. But most importantly, Keystone does not see any sensitive user information (passwords), as was the case in the SQL and LDAP-based backends.
To enable this option in Horizon, certain options need to be enabled in Horizon’s configuration file. These options and more details about this work will be further highlighted in “5.6 A Practical Guide to Setting Up SSO with Google”, including screenshots. For this section, we will simply outline the steps pointing out where the differences happen when compared to the previous flow.
In this guide, we will reuse the Bluepages LDAP server as our identity store. However, we will also use WebSphere Liberty as our Identity provider, since it has capabilities to act as an OpenID Connect provider.
To proceed with this section, adding WebSphere Liberty to your environment is necessary. IBM’s WebSphere Liberty runtime and its extensions are available for free. To download the runtime, navigate to https://developer.ibm.com/wasdev/downloads/liberty-profile-using-non-eclipse-environments/.
Once the jar files have been installed, we need to use the built-in feature manager to install the OpenID Connect Server feature.
$ ./wlp/bin/featureManager install openidConnectServer-1.0
Before we configure WebSphere Liberty to run as an OpenID Connect provider, we need to create a server.
$ ./wlp/bin/server create oauthServer
We can now configure the server to point to the Bluepages LDAP backend. The following is an example configuration in ./wlp/usr/servers/oauthServer/server.xml.
<server> <featureManager> <feature>openidConnectServer-1.0</feature> <feature>ssl-1.0</feature> <feature>appSecurity-2.0</feature> <feature>servlet-3.0</feature> </featureManager> <ldapRegistry id="bluepages" realm="w3" host="bluepages.ibm.com" port="389" ignoreCase="true" baseDN="o=ibm.com" ldapType="IBM Tivoli Directory Server" > <idsFilters userFilter="(&(emailAddress=%v)(objectclass=person))" groupFilter="(&(cn=%v)(objectclass=groupOfUniqueNames))" userIdMap="*:emailAddress" groupIdMap="*:cn" groupMemberIdMap="groupOfUniqueNames:uniquemember" /> </ldapRegistry> <keyStore id="defaultKeyStore" password="insecurePass"/> <httpEndpoint host="localhost" httpPort="9080" httpsPort="9443" id="defaultHttpEndpoint"/> <oauth-roles> <authenticated> <special-subject type="ALL_AUTHENTICATED_USERS" /> </authenticated> </oauth-roles> <openidConnectProvider id="OP" oauthProviderRef="Oauth" /> <oauthProvider id="Oauth" > <localStore> <client name="rp" secret="LDo8LTor" displayname="rp" introspectTokens="true" redirect="https://localhost:9443/oauthclient/redirect.jsp" scope="openid profile email phone address" preAuthorizedScope="openid profile" enabled="true"/> </localStore> </oauthProvider> </server>
The configuration above does the following:
ldapRegistry
option, we point it to our Bluepages LDAP with a user and group filter, not unlike the ones we would provide for Keystone.oauthProvider
option specifies the OAuth client ID and client secret. These are rp
and LDo8LTor
, respectively. It also specifies which port the process is run on. In this case it’s 9443.keyStore
option to make things easy to test.Endpoints are specified in Liberty’s documentation, but we’ll outline them here briefly:
/oidc/endpoint/OP/token
/oidc/endpoint/OP/introspect
Now let’s start the server to test if the configuration is valid.
$ ./wlp/bin/server start oauthServer
We can quickly check to see if we can authenticate with the OpenID Connect Provider by providing our own username and password and the client ID and secret. The output should be an access token.
$ curl -k -H "Content-Type: application/x-www-form-urlencoded;charset=UTF-8" -d "grant_type=password&client_id=rp&client_secret=LDo8LTor&username=stevemar%40ca .ibm.com&password=mySecretPassword&scope=profile" https://localhost:9443/oidc/ endpoint/OP/token { "access_token":"kjDVDz7vyUp4maswQbeoaHjN42WFXNSMdBkos1VJ", "token_type":"Bearer", "expires_in":7200, "scope":"profile", "refresh_token":"1tqvETSuEo9oiBBCbsabjfY0W1fyvbpH5rTTf3i6979BEsDOp4" }
Optionally, we can immediately retrieve an OpenID Connect. We can exchange the access token for an OpenID Connect claim, which contains information about the authenticated user, via the introspection endpoint. To do this we supply the client ID and secret and our newly acquired access token.
$ curl -k --user rp:LDo8LTor https://localhost:9443/oidc/endpoint/OP/introspect?token=kjDVDz7vyUp4maswQbeoaH jN42WFXNSMdBkos1VJ { "exp":"1433979091514600", "realmName":"w3", "sub":"[email protected]", "scope":"profile", "grant_type":"resource_owner" "uniqueSecurityName":"uid=123456789,c=ca,ou=bluepages,o=ibm.com" "active":"true", "client_id":"rp", "token_type":"Bearer" "iat":"1433979091511000" "groupIds":"[cn=IBM_Regular_Empl...Canada,ou=memberlist,ou=ibmgroups,o=ibm.com, cn=SWG_Canada,ou=memberlist,ou=ibmgroups,o=ibm.com, cn=Toronto_Lab_VPN,ou=memberlist,ou=ibmgroups,o=ibm.com]" }
Now we must configure Keystone to be able to use the new OpenID Connect provider we created. We have to modify Keystone’s configuration file to enable federated authentication, create the identity provider, protocol, and mapping in Keystone, and lastly we must protect a specific URL to trigger the authentication.
The first (and essential) step in configuring Keystone to use OpenID Connect is to install the Apache Httpd plugin mod_auth_openidc
. Several packages are available at https://github.com/pingidentity/mod_auth_openidc/releases.
$ sudo apt-get install libjansson4 libhiredis0.10 libcurl3 $ sudo dpkg -i libapache2-mod-auth-openidc_1.8.3-1_amd64.deb
Next, we must update /etc/keystone/keystone.conf to support OpenID Connect as an authentication mechanism. In the [auth]
section set the following:
methods = external,password,token,oidc oidc = keystone.auth.plugins.mapped.Mapped
In the [federation]
section set the following:
remote_id_attribute = HTTP_OIDC_ISS
We also need to update Keystone’s virtual host file and add in new OpenID Connect entries to /etc/apache2/sites-available/keystone.conf. The new settings are highlighted below.
LoadModule auth_openidc_module /usr/lib/apache2/modules/mod_auth_openidc.so <VirtualHost *:5000> ... SetEnv HTTP_OIDC_ISS bluepages OIDCOAuthIntrospectionEndpoint "https://localhost:9443/oidc/ endpoint/OP/introspect" OIDCOAuthIntrospectionTokenParamName token OIDCOAuthRemoteUserClaim sub OIDCOAuthClientID rp OIDCOAuthClientSecret LDo8LTor OIDCOAuthSSLValidateServer Off OIDCClaimDelimiter ";" OIDCClaimPrefix "OIDC-" <LocationMatch "/v3/OS-FEDERATION/identity_providers/bluepages/ protocols/oidc/.*?"> Authtype oauth20 Require valid-user LogLevel debug </LocationMatch> </VirtualHost>
We are essentially setting the protected URL, in the LocationMatch
header, and setting various OpenID Connect properties.
Now that we’ve updated Keystone’s configuration file and virtual host file, we need to create a local Keystone group so that users authenticating via federation can be mapped into that group. In this case, we create a group called federated_users
with the role of member
on the project demo
.
$ openstack group create federated_users $ openstack role add member --group federated_users --project demo
Before we create the identity provider, protocol, and mapping entries in Keystone, we can create a file that contains the mapping so as to make our CLI interactions easier. Create the file mapping.json, and remember to update the contents of the local section with the correct ID from the previous command.
[ { "local": [ { "group": { "id": "26daaf649d6a46b998f631da79759230" } } ], "remote": [ { "type": "HTTP_OIDC_GROUPIDS", "any_one_of": [ "cn=SWG_Canada*" ], "regex": true } ] } ]
The result of this mapping will be that any user that is a member of the LDAP group SWG_Canada
will be placed into the Keystone group federated_users
.
The last step is to tie all the pieces together and create the Keystone federation resources; let’s create the identity provider, the mapping, and the protocol.
$ openstack identity provider create bluepages --remote-id bluepages $ openstack mapping create ibm_mapping --rules mapping.json $ openstack federation protocol create oidc --identity-provider bluepages --mapping ibm_mapping
Using the Python-keystoneclient library, we can create a session that uses the OpenID Connect auth plugin. We will create a sample file that can be run to create an unscoped token. We’ll call the file test_oidc.py.
from keystoneclient.contrib.auth.v3 import oidc from keystoneclient import session AUTH_URL = 'http://localhost:5000/v3' IDENTITY_PROVIDER = 'bluepages' PROTOCOL = 'oidc' USER_NAME = '[email protected]' PASSWORD = 'boogabooga' CLIENT_ID = 'rp' CLIENT_SECRET = 'LDo8LTor' ACCESS_TOKEN_ENDPOINT = 'https://localhost:9443/oidc/endpoint/OP/token' def main(): oidc_plugin = oidc.OidcPassword( AUTH_URL, IDENTITY_PROVIDER, PROTOCOL, username=USER_NAME, password=PASSWORD, client_id=CLIENT_ID, client_secret=CLIENT_SECRET, access_token_endpoint=ACCESS_TOKEN_ENDPOINT) s = session.Session(verify=False) access_info = oidc_plugin.get_unscoped_auth_ref(s) print(access_info) if __name__ == '__main__': main()
When ran, the output will be an unscoped Keystone token that contains information about the federated user and the groups they have access to:
$ python test_oidc.py { u'extras': {}, u'methods': [u'oidc'], u'version': 'v3', u'audit_ids': [u'F4FbnLlaQ_SsU4xrD_jE7w'], u'issued_at': u'2015-06-11T07:36:00.337835Z', u'auth_token': '87c736b04338482a94cf951ee0798a76', u'expires_at': u'2015-06-11T08:36:00.337487Z', u'user': { u'OS-FEDERATION': { u'identity_provider': {u'id': u'bluepages'}, u'protocol': {u'id': u'oidc'}, u'groups': [{u'id': u'26daaf649d6a46b998f631da79759230'}] }, u'domain': {u'id': u'Federated', u'name': u'Federated'}, u'id': u'stevemar%40ca.ibm.com', u'name': u'[email protected]' } }
In this guide, we will discuss how to set up Keystone and Horizon to allow for single sign-on. In this case, use Google as the Identity Provider and allow users with valid Google accounts to federate into our Keystone deployment.
Similar to “5.5.2 Configuring Keystone to Use OpenID Connect”, we need to configure Keystone to use an OpenID Connect provider. First, we install the Apache Httpd plugin mod_auth_openidc
. Several packages are available at https://github.com/pingidentity/mod_auth_openidc/releases.
$ sudo apt-get install libjansson4 libhiredis0.10 libcurl3 $ sudo dpkg -i libapache2-mod-auth-openidc_1.8.3-1_amd64.deb
Next, we must update /etc/keystone/keystone.conf to support OpenID Connect as an authentication mechanism. In the [auth]
section set the following:
methods = external,password,token,oidc oidc = keystone.auth.plugins.mapped.Mapped
In the [federation]
section, we must set a list of trusted dashboards, which is the fully qualified domain name of the host that is running Horizon.
remote_id_attribute = HTTP_OIDC_ISS trusted_dashboard = http://sso-demo.test.ibmcloud.com/auth/websso/
We also need to update Keystone’s virtual host file and add in new OpenID Connect entries to /etc/apache2/sites-available/keystone.conf. The new settings are highlighted below.
LoadModule auth_openidc_module /usr/lib/apache2/modules/mod_auth_openidc.so <VirtualHost *:5000> ... OIDCClaimPrefix "OIDC-" OIDCResponseType "id_token" OIDCScope "openid email profile" OIDCProviderMetadataURL "https://accounts.google.com/.well-known/ openid-configuration" OIDCClientID "78026256901-9oice6ionj19voicnrii2lfl86a1i4rp.apps. googleusercontent.com" OIDCClientSecret d6TiBYA3qQUuzlR91Q-YzpJA OIDCCryptoPassphrase openstack OIDCRedirectURI "http://sso-demo.test.ibmcloud.com:5000/v3/auth/ OS-FEDERATION/websso/oidc/redirect" <Location ~ "/v3/auth/OS-FEDERATION/websso/oidc"> AuthType openid-connect Require valid-user LogLevel debug </Location> </VirtualHost>
We are essentially setting the protected redirect URL, in the LocationMatch
header, and setting various OpenID Connect properties. Note that the OIDCClientID
and OIDCClientSecret
come from a Google developer project.
Now that we’ve updated Keystone’s configuration file and virtual host file, we need to create a local Keystone group so users authenticating via federation can be mapped into that group. In this case, we create a group called federated_users
with the role of member on the project demo
.
$ openstack group create federated_users $ openstack role add member --group federated_users --project demo
Before we create the identity provider, protocol, and mapping entries in Keystone, we can create a file that contains the mapping so as to make our CLI interactions easier. Create the file mapping.json, and remember to update the contents of the local section with the correct ID from the previous command.
[ { "local": [ { "group": { "id": "4e48b0c139c94cf088a665d919ee10f1" } } ], "remote": [ { "type": "HTTP_OIDC_ISS", "any_one_of": [ "https://accounts.google.com" ] } ] } ]
The result of this mapping will be that any user that comes from Google, essentially having the issuer set to http://accounts.google.com
, will be placed into the Keystone group federated_users
.
The last step is to tie all the pieces together and create the Keystone federation resources. Let’s create the identity provider, the mapping, and the protocol.
$ openstack identity provider create google --remote-id https://accounts.google.com $ openstack mapping create google_mapping --rules mapping.json $ openstack federation protocol create oidc --identity-provider google --mapping google_mapping
We must also copy the sso_callback_template.html file to /etc/keystone/. This is simply a small script that posts an unscoped token back to Horizon. It is necessary since the original location of Horizon is lost in all the redirects. The script itself may be overridden by the deployer. What is provided by Keystone is the minimum needed.
/$ cp /opt/stack/keystone/etc/sso_callback_template.html /etc/keystone
The last step is to set a few values in Horizon’s configuration file, located at horizon/openstack_dashboard/local/local_settings.py.
OPENSTACK_KEYSTONE_URL = "http://sso-demo.test.ibmcloud.com:5000/v3” OPENSTACK_API_VERSIONS = { "identity": 3 } WEBSSO_ENABLED = True WEBSSO_CHOICES = ( ("credentials", _("Keystone Credentials")), ("oidc", _("OpenID Connect")) ) WEBSSO_INITIAL_CHOICE = "credentials"
The OPENSTACK_KEYSTONE_URL
and OPENSTACK_KEYSTONE_URL
options are not specific to implementing single sign-on, but are necessary to be changed to point to v3 of the Identity API. These values indicate the authentication URL to use for Keystone and which version to use.
The WebSSO options are fairly straightforward, with WEBSSO_ENABLED
being the main option to enable single sign-on, and WEBSSO_CHOICES
being the contents of a drop-down menu. The contents of WEBSSO_CHOICES
are broken up into the federation protocol name and the name the user will see on the Horizon landing page. The term credentials
is a reserved keyword and should be added to the list to allow service and admin accounts stored in SQL to authenticate. Finally, the option WEBSSO_INITIAL_CHOICE
sets the default value of the drop-down menu the user selects.
Once completed, restart Apache to update both Keystone and Horizon.
$ sudo service apache2 restart
Congratulations! That is the complete guide to configuring both Keystone and Horizon to use single sign-on. In the next subsection, we will step through a visual guide that shows the flow from a user’s perspective.
Step 1: We hit the Horizon landing page, with Keystone Credentials selected as the default authentication method. We are still able to authenticate with a username and password of a user in SQL or LDAP.
Step 2: We proceed by selecting the drop-down menu and choosing the OpenID Connect option. This will cause the username and password fields to be removed. Let’s go ahead and opt to Sign In.
Step 3: Assuming Keystone and Horizon are properly configured, we should be redirected to a Google-branded sign in page. Simply authenticate with Google with your usual username and password, even if multi-factor authentication is enabled—it doesn’t matter, as long as you can authenticate. The Apache modules will be able to read the incoming OpenID Connect claim and set user attributes as HTTP headers.
Step 4: Once Keystone invokes the correct mapping engine and returns a token back to Horizon, the user should be able to log in to view and perform tasks as normal.
Similar to LDAP, the federation capabilities of Keystone require extra dependencies that are not installed by default. This includes the Python library pysaml2 and the package xmlsec1. To install these on Ubuntu, use the following commands:
$ sudo pip install pysaml2 $ sudo apt-get install xmlsec1
In our earlier example of authenticating via OpenID Connect and a command line, we were leveraging the “Resource Owner Password Credential” flow of the OpenID Connect specification. In our example of WebSphere Liberty acting as an Identity Provider, that particular flow is enabled. Most social media logins will disable this flow, since they do not want their users to enter passwords into a non-branded or trusted medium.
Users are able to authenticate via SAML using the command line as well as a browser. The Identity Provider must be configured to expose the Enhanced Client or Proxy (ECP) flow of the SAML specification. Keystone has supported this mode of authentication since the Icehouse release.
3.131.13.194