owncloud / ocis

:atom_symbol: ownCloud Infinite Scale Stack
https://doc.owncloud.com/ocis/next/
Apache License 2.0
1.38k stars 181 forks source link

Authorization headers need to be passed on #53

Closed butonic closed 4 months ago

butonic commented 4 years ago

The current implementation of reva uses the gateway to authenticate, route and trace all requests in a stateless service. The idea is that we can set up the actual storage, sharing and user provider services as needed and scale the gateway as needed, because all of the 'backend' services need to authenticate requests.

Currently, the gateway replaces whatever credentials are used with a jwt token that contains the user id and other metadata like displayname, email and username.

The problem is that the original credentials are not passed on. This creates a problem when they are needed to access other services in the organization. I can identify two use cases:

I prototyped using the bearer token as the internal jwt token in https://github.com/cs3org/reva/pull/382. While that works nice for jwt based self signed tokens as used by kopano it is horrible for the recommended opaque tokens that need an additional introspection request against the idp. Every reva process would need to to the introspection again, hammering the IdP with introspection requests.

So, I went on and implemented https://github.com/cs3org/reva/pull/384 which is needed to make oidc work with ldap or the kapi graph as a user backend. Currently, the gateway will authenticate credentials and then look up the user metadata based on the userid.

It seems reasonable to combine the two PRs and

  1. extend the oidc token manager with a redis cache for introspection requests. jwt based tokens work just fine
  2. allow a chain of token strategies, because it might be a bearer token, a kerberos ticket or a jwt token we created ourself when replacing basic auth credentials.

Writing this down I realized that another requirement we have is being able to extend whatever authentication is used with additional roles. That is only possible if we use our own jwt token. So we need to do this the other way round: store the original credentials in our jwt token. While it seems counter intuitive to store a jwt in a jwt it is the only way to pass on the original jwt bearer token (or a kerberos token) and additional metadata.

labkode commented 4 years ago

That is only possible if we use our own jwt token. So we need to do this the other way round: store the original credentials in our jwt token. While it seems counter intuitive to store a jwt in a jwt it is the only way to pass on the original jwt bearer token (or a kerberos token) and additional metadata.

The Reva JWT token is the authoritative token, independently if the authentication is oidc or basic auth. The JWT token can include information how it was generated and can include any additional metadata that is not given by the Idp. For storing sensitive information (basic auth, kerberos token) the token needs to be opaque and not only signed but encrypted. For non-sensitive data, like OIDC token, it can be only signed. Having the Reva token you can create sessions with the application independently of the authentication provided rather than having to authenticate every single request, i.e, if providing basic auth you don't want to validate the user credentials every time and the same applies for OIDC and any other mechanism.

After Phoenix does the authentication with Reva, Reva can easily set a cookie that is the Reva token and further requests will contains the OIDC token as Bearer and Reva token as cookie. I'd like that Phoenix sets the Bearer token to the Reva rather than storing the OIDC token that is only required for initial authentication and the Reva token as a session can have user-friendly expiration times rather than revalidating the OIDC token with refresh requests to the IDP.

Another concern raised for OCIS is that you want to open a browser tab from the desktop client without having to authenticate and you thought about using OIDC to have that behaviour, but that is only a technology. If the desktop client were using basic auth and the server was also using basic auth you can achieve the same. For that reason, by having the Reva token as the Bearer token makes more sense as it is authentication agnostic and is recognized by the server as a valid token, allowing to also open browser tabs from the desktop client.

Now the challenging point is that you want to use the authenticate token (OIDC in this case) to connect to Kopano for getting the information of the logged-in user? For any user? I need you to elaborate on that. If the OIDC is needed, then it can be encoded in the Reva token as metadata and the user manager implementation talking to Kopano can use it.

butonic commented 4 years ago

Now the challenging point is that you want to use the authenticate token (OIDC in this case) to connect to Kopano for getting the information of the logged-in user? For any user? I need you to elaborate on that. If the OIDC is needed, then it can be encoded in the Reva token as metadata and the user manager implementation talking to Kopano can use it.

Exactly that. Kopano provides a graph api that abstracts away different user managers, emal servers, calendar implementations... It would allow us to look up user metadata, search for users / share recipients, create guest users, dependting on the permissions associated with the bearer token.

Now a client like phoenix or the desktop client will get an oauth2 bearer token using the appropriate oidc flow. The user has to give his consent that these clients are allowed to access reva. I am a little unsure about who has to give his consent to grant access to the api. The two options we have are:

  1. the admin consents to reva acessing the kapi with a system user during installation. reva acts as a client to the kapi with its own refresh token and acess tokens...
  2. the user consents to reva acessing the kapi in his name. reva forwards the token presented by whatever client to the kapi.

I dislike system users, and would prefer to use the token of the client, however AFAIU oidc tokens carry the aud (audience) claim and a relying party should ignore any tokens not intended for them. In the above scenario kapi and reva are two different relying parties.

... reads up on the OIDC ID Token spec ...

aud REQUIRED. Audience(s) that this ID Token is intended for. It MUST contain the OAuth 2.0 client_id of the Relying Party as an audience value. It MAY also contain identifiers for other audiences. In the general case, the aud value is an array of case sensitive strings. In the special case when there is one audience, the aud value MAY be a single case sensitive string. ... azp OPTIONAL or REQUIRED. Authorized Party - the party to which the ID Token was issued. If present, it MUST contain the OAuth 2.0 client_id of the party that will be using it. This Claim is only REQUIRED when the party requesting the ID Token is not the same as the sole audience of the ID Token. It MAY be included even when the Authorized Party is the same as the sole audience. The azp value is a case sensitive string containing a StringOrURI value.

So, the IdP needs to fill aud with the client_id for phoenix, or the desktop/mobile app, the client_id for reva, the client_idfor kapi (if they differ) and maybe other client_id the token is allowed to use. Furthermore, it needs to set azp with the client_id of phoenix or the mobile/desktop client that requested the token.

At least that is what I read from the spec. In https://github.com/IdentityServer/IdentityServer3/issues/1365 however, the argument is made that scope and aud are semantically equivalent. They are both used to restrict access ... it also points to https://tools.ietf.org/html/draft-ietf-oauth-token-exchange-08#section-2.1.1 which is currently at v19, still carrying the same 2.1.1. Relationship Between Resource, Audience and Scope:

When requesting a token, the client can indicate the desired target service(s) where it intends to use that token by way of the "audience" and "resource" parameters, as well as indicating the desired scope of the requested token using the "scope" parameter. The semantics of such a request are that the client is asking for a token with the requested scope that is usable at all the requested target services. Effectively, the requested access rights of the token are the cartesian product of all the scopes at all the target services.

But how should the client know what other background microservices are going to be called by reva? Clearly, I just don't get it. Or I am making it harder than it is. Needs more :coffee: to sink in. Will check 1.1. Delegation vs. Impersonation Semantics and the whole draft ... it seems to describe how to deal with this ...

One common use case for an STS (as alluded to in the previous section) is to allow a resource server A to make calls to a backend service C on behalf of the requesting user B.

will report back later

labkode commented 4 years ago
  • the admin consents to reva acessing the kapi with a system user during installation. reva acts as a client to the kapi with its own refresh token and acess tokens...

This looks the right approach to me. It works like an LDAP connection will work, you have system user to bind LDAP connection and then you query for what you want.

The usage of a user OIDC token to connect back to Kopano APIs by micro-services that can live anywhere and not necessary under the same domain I think it won't work.

Going for approach 1) is simpler and less hacky.

fschade commented 4 months ago

Please open again if the ticket is still relevant