oauth design (#441)

This commit is contained in:
Firstyear 2021-05-19 12:00:32 +10:00 committed by GitHub
parent 08cf9a8dc7
commit 152bd79d6d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 193 additions and 167 deletions

View file

@ -1,167 +0,0 @@
Claims Design
-------------
Claims are a way of associating privileges to sessions, to scope and limit access of an identity.
This can be based on what credentials were used to authenticate, by the user requesting claims
for a session, or by time limiting claims.
These tend to fall into three major categories
Default Interactive Claims
--------------------------
When an identity authenticates they are given a set of claims associated to their interactive
login. This could include the ability to trigger a claim request or to view personal details.
Static Application Claims (or lack of)
--------------------------------------
Static application passwords (IE a device imap/smtp password) should have a claim to "email"
but not to read personal data and shouldn't be able to change passwords etc.
Ephemeral Claims
----------------
These are permissions that are limited in time - they *must* be requested and generally require
re-authentication with a subset of credentials. This can include the ability to alter ones own
account or time limiting admins ability to alter other accounts.
Detailed Design
---------------
As a result of these scenarios this leads to the following required observations.
* Claims must be associated to the credentials of the account
* Possible claims that could be assigned derive from membership to a group
* Claims must be understood by access controls of the server
* Claims must be present in user auth tokens for end applications to be able to verify
* Two types of claims exist - ephemeral and static
This leads to a pseudo design such as:
class: claim
name: claim_email
claim_name: email
member: account_1
class: claim
name: claim_unused
claim_name: unused
class: system, claim
name: claim_interactive
claim_name: interactive
class: claim, claim_ephemeral
name: claim_alter_self
claim_name: alter_self
claim_lifetime: 300 # seconds
member: account_1
name: account_1
...
primary_credential: {
type: password|webauthn|password+webauthn
claims: [ claim_alter_self ] //note that interactive is implied
}
application_credentialn: {
name: iphone imap password
type: generated_password
claims: [ claim_email ]
}
When we authenticate with the email password, because there is no lifetime this becomes a static
claim:
UserAuthToken {
name: account_1
claims: [ email ]
}
If we authenticate with the primary credential, the static claims are initially issued, and because
it's the primary token, we get the implied system interactive claim.
UserAuthToken {
name; account_1
claims: [ interactive ]
}
To have the "alter_self" claim, we must perform an auth-request-claim operation which re-verifies
a credential. This is a subset of the Auth operation
auth-request-claim ----> verify aci of request (are you interactive? )
<---- return challenge
send password/cred ----> verify credential
<---- update UAT with ephemeral claim
Then the UserAuthToken would be:
UserAuthToken {
name; account_1
claims: [ interactive, alter_self(expire_at_time) ]
}
This means:
* Consuming applications need to verify the claim list
* They need te verify the claim's expiry times.
For kanidm, to use claims in access controls, these must become filterable elements. On
UAT to Entry as part of the event conversion we will perform
load entry to member
for each claim in UAT:
if claim is not expired
alter memory entry -> add claim
ACP's can then have filters such as:
Eq('claim', 'alter_self')
This implies that claim's are in schema to allow filter construction and validation, and in
the protected module to prevent their creation.
Questions
---------
We should only be able to request claims on interactive (primary) credential sessions. How should
we mark this? I think the UAT needs to retain "what credential id" was used to authenticate, and then
emit this to the entry so that it can also be filtered on to determine primary vs application cred.
Claim and other generated attrs must be system protected, even though they have to exist in schema
for filter verification. This likely needs to be added to system_protected plugin to prevent claims
from being added to any entry type.
Once a claim is dynamically added to the entry it must move to a new state that prevents reserialisation to the DB.
Trust Considerations
--------------------
Claims should not be replicated, and are auth-silo specific. This is because
trusts as designed are about account and group sharing, rather than about detailed privilege or
resource granting in the trusting domain.
Because claims should be associated to groups, we can also apply account pw policy to groups.
This means that at the very least we have to consider replication of credential metadata though
so that the trusting domain can assign the claims somewhere (this metadata will be needed for
account cred policy and group membership later anyway). For example:
spn: claire@domainb
class: [trustedaccount, object]
trustedcredential: [ name, id, claims ]
This way when the account authenticates to the trusting domain, because the credential ID that was
used is in the UAT, this allows the trusting domain to inspect what credential was used, and to
be able to assign it's domain local claims to the session. This could then have a similar work
flow when ephemeral claims are needed.
mental note: because groups will define account policy, when a trusted account is a member of a group
and it doesn't meet that groups account policy requirements, it should be listed in the uat as
a rejected group so that we can easily diagnose when an account is insufficient to receive that group
or that claim as a result. This may affect how we treat memberof on the session though when
we do UAT to entry. An argument could be made to strip the memberofs when they are in the rejected
list ...

161
designs/oauth.rst Normal file
View file

@ -0,0 +1,161 @@
Oauth + Scopes + Claims
-----------------------
Oauth is a web authorisation protocol that allows "single sign on". It's key to note
oauth is authorisation, not authentication, as the protocol in it's default forms
do not provide identity or authentication information, only information that
an entity is authorised for the requested resources.
Oauth can tie into extensions allowing an identity provider to reveal information
about authorised sessions. This extends oauth from an authorisation only system
to a system capable of identity and authorisation. Two primary methods of this
exist today: rfc7662 token introspection, and openid connect.
High Level Process
------------------
A client (user) wishes to access a service (resource, resource server). The resource
server does not have an active session for the client, so it redirects to the
authorisation server to determine if the client should be allowed to proceed, and
has the appropriate permissions (scopes).
The authorisation server checks the current session of the client and may present
a login flow if required. Given the identity of the client known to the authorisation
sever, and the requested scopes, the authorisation server makes a decision if it
allows the authorisation to proceed. The client is also prompted to consent to the
authorisation.
If successful the client is redirected back to the resource server with an authorisation
code. The resource server the contacts the authorisation server directly with this
code and exchanges it for a valid token that may be provided to the client.
The resource server may optionally contact the token introspection endpoint about the
provided oauth token, which yields extra metadata about the identity that holds the
token and completed the authorisation. This metadata may include identity information,
but also may include extended metadata, sometimes refered to as "claims". Claims are
information bound to a token based on properties of the session that may allow
the resource server to make extended authorisation decisions without the need
to contact the authorisation server to arbitrate.
In this model, Kanidm will function as the authorisation server.
Kanidm UAT Claims
-----------------
To ensure that we can filter and make certain autorisation decisions, the Kanidm UAT
needs to be extended with extra claims similar to the token claims. Since we have the
ability to strongly type these, we can add these to the UAT. These should include.
* The UUID of the authenticating credential
* The expiry time of this session
* the classification of authentication used (MFA, SFA, Cryptographic)
* The expiry time of any elevated permissions
* If the session is "interactive" IE from a true human rather than an API pw.
* If the user is anonymous (?)
The UAT should be signed with ECDSA so that client applications may inspect the content
IE the session expiry time. This may also allow offline validation.
The ECDSA public key for this should be stored in the Kanidm "domain" configuration. The
private key should also be stored in this configuration, but thought will be needed about how
to handle this with replication securely IE readonly servers.
HTTP Endpoints
--------------
We should expose the following endpoints:
* /oauth/authorise
* /oauth/token
* /oauth/token/introspect
All api responses must have:
::
Cache-Control: no-store
Pragma: no-cache
Resource Servers
----------------
For a resource server to work with the authorisation server, it must be a registered
application within the authorisation server.
Each registered resource server will have an associated secret for authentication. The
most simple for of this is a "basic" authorisation header.
This resource server entry will nominially list what scopes map to which kanidm roles,
which scopes are "always" available to all authenticated users. Additionally, it may
be that we have an extra set of "filter rules" to allow authorisation decisions to be
made based on other factors like group membership.
::
class: oauth_resource_server
class: oauth_resource_server_basic
oauth_rs_name: String,
oauth_rs_basic_secret: String,
# To validate the redirect root
oauth_rs_origin: String/URI
# Scopes that apply to all users
oauth_rs_scope_implicit: String
# Scopes that map to groups which will be enforced.
oauth_rs_scope_map: (String, reference)
# Filter of accounts that may authorise through this.
oauth_rs_account_filter: Filter
# A per-resource server fernet key for token/codes.
# Allows reset per/application in case of suspect compromise.
oauth_rs_token_key: String
The returned authorisation code should be fernet encrypted and contains the unsigned UAT content of the authorised
user.
The provided oauth token for this method will be encrypted with the fernet key of the related
resource server. It will contain the unsigned uat of the account in authorised, allowing token
introspection/reflection without needing to access the database.
Token Introspection
-------------------
Claims will be mapped to a kanidm namespace. Otherwise the rfc will be followed.
Security
--------
Only PKCE Oauth 2.0 clients are accepted today. Alternately stronger exchange types may be considered
in the future.
The default filter of accounts will exclude anonymous and tombstones/recycled.
Should the default filter only allow interactive accounts to participate in this work flow?
Test Cases / Use Cases
----------------------
roles such as idm_admin/admin should also require claim=sudo to use.
To change your own details (self write) sudo should be required.
read_self, mail etc should always be granted.
Anonymous should not have access to any claims.
sudo time expiry
The ability to use oauth should require
Links
-----
Oauth2: https://tools.ietf.org/html/rfc6749
pkce: https://tools.ietf.org/html/rfc7636
token introspection: https://tools.ietf.org/html/rfc7662
bearer: https://tools.ietf.org/html/rfc6750
device authorisation grant: https://datatracker.ietf.org/doc/html/rfc8628
claims ad krb: https://syfuhs.net/2017/07/29/active-directory-claims-and-kerberos-net/
openid connect: https://openid.net/developers/specs/

32
designs/sudo.rst Normal file
View file

@ -0,0 +1,32 @@
Sudo Mode
---------
To ensure that certain actions are only performed after re-authentication, we should introduce
a sudo mode to kanidm. This relies on some changes from Oauth.rst (namely interactive session
identification).
Only interactive sessions (IE not api passwords or radius) must be elligble for sudo mode.
Sudo mode when requested will perform a partial-reauthentication of the account using a single
factor (if mfa). This is determined based on the credential uuid of the associated session.
When entered, a short sudo expiry timer is attached to the UAT, which is re-issued after the
re-authentication.
During UAT to entry processing for api calls, the sudo timer will be checked. If current
time is less than the expiry, then the phantom attribute "sudo" which is boolean, will be set
to the entry. If it is not present, or invalid, it will be set to "false".
This will allow filtering on sudo=true, meaning that certain default access controls can be
altered to enforce that they require sudo mode.
Some accounts by default represent a high level of privilege. These should have implicit sudo
granted when they are autheticated. This will be based on a group membership idm_hp_implicit_sudo
and should only apply to admin/idm_admin by default. This will pin the sudo expiry to the expiry
time of the session (rather than a shorter time).
Some accounts should never be able to enter sudo mode, and this will be based on the lack of
appropriate credentials. IE anonymous can never enter sudo mode, and will always fail. This
will allow the removal of a number of hardcoded anonymous exceptions in the IDM server, allowing
us to use the acp's to enforce rules instead.