Merge "OpenID Connect improved support"

This commit is contained in:
Zuul 2020-01-02 15:08:27 +00:00 committed by Gerrit Code Review
commit 73b39916e7
1 changed files with 231 additions and 0 deletions

View File

@ -0,0 +1,231 @@
..
This work is licensed under a Creative Commons Attribution 3.0 Unported
License.
http://creativecommons.org/licenses/by/3.0/legalcode
===============================
Improved OpenID Connect Support
===============================
`bp improved-oidc-support <https://blueprints.launchpad.net/keystone/+spec/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.
.. _OpenID Connect specification: https://openid.net/specs/openid-connect-core-1_0.html
.. _Oauth 2.0 specification: https://tools.ietf.org/html/rfc6749#section-1.1
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.
.. _configuration guide: https://developer.ibm.com/opentech/2015/06/17/use-websphere-liberty-as-an-openid-connect-provider-for-openstack
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.
.. _OpenID Connect certified implementations: https://openid.net/developers/certified/
.. _pyoidc: https://github.com/OpenIDC/pyoidc
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>`