Add spec for MFA auth receipts

Change-Id: Ia8f25cf84070c271fc2ffb9f8fd115f55b3e428e
This commit is contained in:
Adrian Turjak 2018-03-16 16:17:41 +13:00
parent 7364e98854
commit d3695a1b5d
2 changed files with 324 additions and 0 deletions

View File

@ -14,6 +14,17 @@ Project Documentation:
keystone keystone
======== ========
Rocky approved specs:
`Rocky Roadmap <https://trello.com/b/wmyzbFq5/keystone-rocky-roadmap>`_
.. toctree::
:glob:
:maxdepth: 1
specs/keystone/rocky/*
Queens approved specs: Queens approved specs:
`Queens Roadmap <https://trello.com/b/5F0h9Hoe/keystone-queens-roadmap>`_ `Queens Roadmap <https://trello.com/b/5F0h9Hoe/keystone-queens-roadmap>`_

View File

@ -0,0 +1,313 @@
..
This work is licensed under a Creative Commons Attribution 3.0 Unported
License.
http://creativecommons.org/licenses/by/3.0/legalcode
================
MFA Auth Receipt
================
`mfa-auth-receipt <https://blueprints.launchpad.net/keystone/+spec/
mfa-auth-receipt>`_
To provide proper usable multi-factor authentication (MFA) support in
OpenStack we need to return an auth receipt to the user representing partial
authentication, which can be returned to Keystone as part of a challenge
response process.
Problem Description
===================
As of Ocata Keystone supports setting auth rules on a given user and
potentially requiring them to auth with multiple methods. This is a good
implementation of MFA, but the problem rests in a user or service knowing ahead
of time how they need to auth. Most standard MFA systems are challenge response
based. You auth with one of your factors, then are given a receipt of that
partial authentication, which you can return along with the missing pieces.
Keystone needs such a mechanism because without it we can't realistically
consume MFA anywhere else in OpenStack in a manner that makes sense for users.
The original proposal for MFA in Keystone actually included this concept but
was never implemented:
`multi-factor-authn <https://blueprints.launchpad.net/keystone/+spec/
multi-factor-authn>`_
The concept of a half-token was discussed, but it never really got implemented
in a way we could use for MFA.
Proposed Change
===============
The proposal is to add a new mechanism which returns a receipt to the user on a
partially successful auth attempt. This would in essence be very similar to a
token, and ideally use a similar system for storage (fernet, jwt) to keep
internal logic consistent and reusable.
When a user successfully authenticates with one of their many auth rules, we
can infer that they are who they say they are, and then return to them a
receipt that contains information about missing auth rules.
If a user attempts auth with multiple methods and most fail but at least one
succeeds, we do not return a receipt, but we do include json error data as to
what methods failed, and which did not.
Using a receipt to get a new receipt with extra auth methods should ideally be
possible. If you need three auth methods to get a token, you should be able to
add to your receipt until it can become a token.
This receipt would allow no API interaction, but it would be proof that
certain auth methods have been completed. The expiry for the receipt would
default to a fairly short time-frame (5 minutes).
Receipt Response
----------------
The return code for this would be ``401`` as the user is still unauthorized,
with the response header containing the receipt id, and the body containing
information about the receipt which they need to continue the auth process.
In addition this response would be explicitly setup to only occur when a user
with auth rules successfully auths with one of the methods in any of their auth
rules.
The receipt id (the encrypted payload) would be included in the header of the
``401`` response in the same fashion as a token is but under the header
``OPENSTACK-AUTH-RECEIPT``.
An example response::
{
"receipt": {
"methods": [
"password"
],
"user_id": "423f19a4ac1e4f48bbb4180756e6eb6c",
"expires_at": "2015-11-06T15:32:17.893769Z"
},
"required_auth_methods": [
['password', 'totp'],
['password', 'option2'],
['password', 'option3', 'option4']
]
}
``required_auth_methods`` would be a list of rules that include the
successfully authenticated methods and not a list of all of the user's auth
rules. The methods in the receipt do not entirely have to be all from the same
auth rule, the receipt contains valid methods, and what rules they could apply
to, so it's an intersection test.
Another example response::
{
"receipt": {
"methods": [
"password", "option3"
],
"user_id": "423f19a4ac1e4f48bbb4180756e6eb6c",
"expires_at": "2015-11-06T15:32:17.893769Z"
},
"required_auth_methods": [
['password', 'option2'],
['option3', 'option4']
]
}
In this above case, the user now has two optional MFA paths off the same
receipt despite there being no overlap between the two auth rules.
Receipt payload
---------------
The receipt will not include scope data as it isn't relevant to it. A user will
supply scope when using the receipt and trying to get a token, Keystone will
not look at the receipt for scope. The only reason to potentially introduce
scope to the receipt is if and when we introduce auth rules based on scope.
The actual provider for the receipt should be configurable in the same fashion
as token, and similar types (fernet, jwt). In the case of fernet the key
repository should ideally be able to be shared with the token provider, but
configuration should be possible to split the repo. This minimises potential
pain for deployers and gives them time before they need to add a second key
repository.
Ideally any code we can share with the token providers we do, and move as much
as makes sense into common utils. Much like tokens, we even handle key
rotation in the same way. Accept receipts encrypted by the current and last key
but issue new ones with the current key.
The receipt data itself (before encryption) would likely be::
{
"methods": [
"password"
],
"user_id": "423f19a4ac1e4f48bbb4180756e6eb6c",
"issued_at": "2015-11-06T14:32:17.893797Z",
}
``methods`` in the above payload being the valid and already satisfied auth
methods for that receipt.
There isn't much data Keystone itself needs that it can't get from database
as long as it knows the user id. All we need to pass back is what methods have
already been validated, when it was issued at (for expiry checking), and the
user the receipt applies to (so we can match it with the user in the auth
request).
Receipt processing
------------------
When auth is continued with a receipt a user must supply the receipt id in the
``OPENSTACK-AUTH-RECEIPT`` header. When Keystone sees an auth attempt with
that header it will use the set receipt provider to decrypt the payload (id),
and will then treat the ``methods`` as defined in the receipt as having been
already satisfied as part of auth, and auth continues with the new user
supplied methods.
If all given methods are valid, but all the total valid methods including those
from the receipt still do not fulfill any of the auth rules, another receipt is
returned.
If the receipt is expired, don't even process the user supplied auth methods
and fail right away.
A mismatch between receipt user_id and user_id in any of the methods is a
failure.
A failure in any of the user supplied auth methods is a failure, and does not
extend the receipt even if some new methods were valid, but should include
failure details.
If a user supplies a method that was already satisfied in the receipt, the user
supplied method takes precedence, even if it fails.
All of these failures return a ``401``.
Alternatives
============
In Ocata when the auth rules were introduced the alternative was put forward
to simply change the error messages of the failed MFA auth to inform the user
why their auth failed. Any services could also parse the errors and infer
what the missing auth was and generate GUI or cli queries back to the user.
This doesn't work for a few reasons. While we can structure the response to be
parsable it still requires that for every auth attempt ALL methods are
supplied. This means an MFA attempt for password+totp needs to cache the
password to resubmit it when it is determined that totp is also needed.
For Horizon that doesn't work. We can't cache the password server-side, and we
shouldn't store it in a cookie or in browser really, and asking the user to
supply their password again isn't good UX.
This alternative also isn't a real challenge response.
Security Impact
===============
A user who authenticates with one successful method now knows what auth rules
can be used for a given user. This isn't particularly unsafe, as while they
know the rules, they can't auth with them. Additionally it is safe to assume
that one successful auth method is enough to prove identity of the user, while
not enough to satisfy authentication requirements.
If any method fails, we still return an error, and auth rules are only exposed
if at least one method was valid.
An additional security concern is that a given receipt could be used to make
multiple tokens in the short window it is valid. Given that users can already
have multiple tokens, this is only a concern if a receipt is intercepted and
completed by someone else. Given though that this receipt exists in the context
of MFA, the second factor should limit this concern quite heavily. In reality
this isn't a problem unique to the auth receipt, but an eventual action we
should take is to do some form of receipt revocations when turned into a token.
Notifications Impact
====================
Notifications will continue to be issued for successful and fail authentication
as today. Issuance of a receipt will not issue a notification.
We may add receipt notifications in the future.
Other End User Impact
=====================
The user may now process the returned data and utilize the receipt to
authenticate in multiple steps.
It is important to note that this change does not affect anyone only doing
auth with no auth rules set. The default auth use case does not change, only
the MFA use case which is not yet fully implemented or used anyway.
Performance Impact
==================
It is expected load and auth-processing-time will increase a small amount due
added encryption and decryption of the receipt.
Other Deployer Impact
=====================
Depending on how we handle fernet/jwt key repositories, deployers may need to
add a second key repository. Ideally we'd allow this to be configurable so at
first they didn't need to do this, but had the option to for added security.
Developer Impact
================
This would only affect authentication workflows in OpenStack and any code
dealing with them. This change though will give us a good programmatic way to
deal with MFA and will mean those tools can handle failures in a useful way.
Implementation
==============
Assignee(s)
-----------
Primary assignee:
* Adrian Turjak - adriant@catalyst.net.nz (IRC adriant)
Work Items
----------
#. Add support for the auth receipt to Keystone and change the auth plugin
layer to return it on one successful auth method out of configured rules
rather than an error, and make the auth layer able to process a receipt for
continued auth.
#. Add support to KeystoneAuth to handle this correctly and infer from this
receipt what other methods are needed, as well as adding proper Multi-Method
support into KeystoneAuth.
#. Work with the CLI and Dashboard teams to build support in for MFA using the
changes made to KeystoneAuth. (optional follow up)
Dependencies
============
N/A
Documentation Impact
====================
Will require extra documentation for new features, but no existing non-MFA auth
paths will change.
We need to add to the docs that MFA receipts also need encryption keys, and
that by default they are shared with fernet. Will need notes added that fernet
key rotation affect the receipts if their repository is shared.
References
==========
* https://adam.younglogic.com/2012/10/multifactor-auth-and-keystone/
* https://blueprints.launchpad.net/keystone/+spec/multi-factor-authn
* https://specs.openstack.org/openstack/keystone-specs/specs/keystone/ocata/per-user-auth-plugin-requirements.html