Improved OpenID Connect Support

bp improved-oidc-support

User access based on OpenID Connect is supported in keystone by leveraging the Apache mod_auth_openidc module and the keystone federation APIs.

This involves setting the Apache server as an OpenID Connect client (Relying Party) that will perform the configured authentication flow, getting the user information (i.e. claims) from the standard OpenID Connect userinfo endpoint. These extra claims include information such as email address, preferred username, name, surname, etc. However, when using the OpenStack CLI, the oidc RP is the CLI itself. The CLI will obtain an access_token from the OpenID Connect Provider, and this token will be exchanged with an Oauth 2.0 protected URL (previously configured by the keystone operator to do token instrospection or local validation using mod_auth_openidc as well). In this case, only the claims contained in the access token or returned by the introspection endpoint will be present, as the userinfo endpoint is specific to OpenID Connect.

The above situation makes it difficult to implement complex policies that rely on the information returned by the userinfo endpoint (such as email address) and it presents a lack of behaviour consistency in the keystone setup. This blueprint aims at fixing this issue, by adding an additional user information retrieval for the OpenID Connect plugin.

Problem Description

Currently OpenID Connect Provider (OP) as an external Identity Provider (IdP) is supported by using:

  • Apache + mod_auth_openidc configured as an OpenID Connect Relying Party (RP).

  • Keystone with the Federation drivers enabled, using the keystone.auth.plugins.mapped.Mapped auth plugin.

According to OpenID Connect specification, the Relying Party should be the Oauth 2.0 client application that will contact the OP in order to get the access/id tokens and eventually the additional user info from the corresponding endpoint. The Oauth 2.0 specification states that a client is an application making protected resource requests on behalf of the resource owner, and with its authorization, not making assumptions on where it is being executed.

In the dashboard case mentioned above, the OpenID RP is the Apache server, therefore Apache is configured with the OpenID Connect client id and secret that will be used for any of the OP grant types supported. Therefore, the keystone administrator would register an OpenID Client in the OP, and add its client id/secret to the mod_auth_openidc configuration. In this case, since everything is handled within Apache and mod_auth_openidc, Keystone receives the access_token, id_token and all the additional grants obtained from the userinfo endpoint in the HTTPD environment variables. The user does not need to do anything with the OP, apart from the usual confirmation that she is authenticating against the RP.

However, if the OpenStack CLIs are being used the RP is not the Apache server, but the CLI (actually, keystoneauth1). In this case, the user has to feed the client id and secret to keystoneauth1, therefore the user has to go to the OP and create a new OpenID Connect client, fetch the discovery document endpoint, client id and client secret and pass all to the library. Then through keystoneauth performing the authentication flow using the requested grant type against the OP, eventually obtaining an access_token. This access_token is then exchanged with an oauth20 protected URL, that needs to be configured to do token introspection against the RP, as in this configuration guide. Since this endpoint is an OAuth 2.0 endpoint it is not able to fetch any additional claims from the userinfo endpoint, as this is something specific to OpenID Connect.

Therefore, the keystone server does not have any additional claims obtained from the userinfo endpoint apart from the ones that are already present in the token, so it is not possible to create any mapping based on this (for example group membership, email address, and so on). The tokens may include additional claims, but this is not mandatory in the standard, being dependant on the OP implementation. For example, Google’s OAuth 2.0 introspection endpoint returns these additional claims.

Following the OpenID Connect terminology, the RP should be keystone, and not the user client. If so, when a user wants to authenticate with OpenID Connect as an IdP the client should contact the federation URL that should be protected with OpenID Connect. Then the authentication flow should be the same as in the horizon+websso case, all handled by mod_auth_openidc and the keystone app will get all the OpenID Connect claims (i.e from the id token and userinfo). This way keystoneauth should not implement any openid logic apart from intercepting the redirect request to the login endpoint and popping out a browser (as in [2]).

[2] https://review.openstack.org/#/c/330006/

However, there are several disadvantages in doing this:

  • Only one grant type can be configured per provider, therefore if a grant type of authz code is configured in the keystone server (the RP) the user won’t be able to use the client credentials grant, even if the OP allows to do so

  • All the code in keystoneauth regarding OpenID connect (that has been released) becomes useless and should be deprecated, as it should not handle any oidc grant type anymore.

  • If the configuread grant type is the Authorization Code, the specification states that interaction with the user agent (e.g. a browser) is needed, therefore this cannot be used (per design) in a service library.

But it has a big advantage:

  • The user does not need to create and manage OpenID Clients in the OP (thus it is not needed to handle the client id, secrets, etc.).

Nevertheless, CLI users may be expecting a similar experience to the one obtained in other cloud providers (like Google Cloud Engine) where the behaviour is like the one we have in place right now (i.e. the user needs to create an OpenID Connect client and user the obtained client id and secret).

However, if we continue with this design, we can leave everything as it is right now, but we need a specific OpenID Connect plugin in keystone that is able to fetch the additional claims from the userinfo endpoint when it only receives an id token. This way keystone will get all these additional claims and the mapping set by the administrator can be based on them. If we do so, operators should configure this plugin, instead of the current mapped plugin (keystone.auth.plugins.mapped.Mapped).

Proposed Change

The proposed change is to implement a specific and native OpenID Connect plugin for Keystone. When this plugin is used, it will handle all the OpenID Connection actions and flows, obtaining all the user claims. Afterwards the plugin will continue as the vanilla mapped plugin, but the additional claims will be present.

Alternatives

The other alternative would be that all the OpenID Connect flow is done by the Apache server where Keystone is running. The advantages and disadvantages of this are described in the “Problem Description” section.

Security Impact

This code will be managing the negotiation between keystone and the OpenID Connect provider. However, there are Python modules (like pyoidc) that are OpenID Connect certified implementations. We would implement only the logic needed on top of these plugins.

Notifications Impact

None.

Other End User Impact

None, with the proposed solution.

Performance Impact

Additional calls need to be made to the external endpoints, that may introduce a delay when responding to the user. This is already happening at the Apache module.

Other Deployer Impact

  • There is an additional dependency on an external module (but this will remove a dependency on the Apache module).

  • It would require additional configuration options and sections (one per provider).

Developer Impact

None.

Implementation

Assignee(s)

Primary assignee: * Alvaro Lopez (aloga)

Other contributors: * None

Work Items

  1. Create an additional mapped plugin, implementing the described logic.

Dependencies

None.

Documentation Impact

New documentation needs to be added on how to configure an OpenID Connect provider.

References

  • OpenID Connect Specifications <https://openid.net/developers/specs/>

  • OAuth 2.0 specification <https://tools.ietf.org/html/rfc6749>

  • Using Google OAuth 2.0 <https://developers.google.com/identity/protocols/OAuth2>

  • Using Google OpenID Connect <https://developers.google.com/identity/protocols/OpenIDConnect>

  • Using the Python client for GCE <https://cloud.google.com/compute/docs/tutorials/python-guide>

  • OpenID Connect certified implementations <https://openid.net/developers/certified/>

  • pyoidc <https://github.com/OpenIDC/pyoidc>