kanidm / kanidm

Kanidm: A simple, secure and fast identity management platform
Mozilla Public License 2.0
2.55k stars 172 forks source link

Allow Oauth2 clients to share a key domain #2545

Open Firstyear opened 6 months ago

Firstyear commented 6 months ago

There are cases where there may be two or more confidental clients that wish to forward access-tokens between them for authentication. In these cases because each client needs to authenticate we currently don't correctly allow multiple clients in one oauth2 signing domain.

This needs to seperate the idea of oauth2 signing domain (that signs and issues the access/refresh/id tokens) and clients (that can authenticate into an oauth2 signing domain for sending tokens).

Currently this can be avoided with public clients, but users may wish for this feature so that leak of an access_token is not able to be used since they lack the client credentials to verify the token.

Blocked by #2513

bella-wmi commented 6 months ago

The usecase we have is the following: We have a client (e.g. an application trying to access our api), a resource-server (our api) and the authorization server (kanidm). The client is a confidential client an therefore is able to save the client credentials in a protected environment. The client will retrieve the access_token by sending a POST request with the client credentials generated by kanidm to the /token endpoint of kanidm. After receiving the access_token the client will do an api-call presenting the access_token. The resource-server receiving the call with the access_token should be able to validate this token. This can happen either by checking the token signature (token has to be encoded as a JWT) or by doing an /introspect POST to kanidm presenting the access_token to be validated inside the payload. The RFC7662 describes, that the /introspect endpoint has to be protected by some sort of authorization. Okta for example allows the introspect to be called via an basic auth presenting the credentials of any client (e.g. credentials of the resource-server) previously created at the authorization server.

Two sequence diagrams: Validate with /introspect endpoint: validate_with_introspect drawio

Validate by checking JWT: validate_by_checking_jwt drawio

Firstyear commented 6 months ago

OAuth2 has the former (verifying the access_token via introspection) as the canonical method, unless we also implemented rfc9068 which effectively turns the access_token into an OIDC style id_token. This also allows weak binding of the access token to a single RS via the resource parameter.

The key domain is a seperate issue to what you are discussing here however. The key domain is where two clients both are issued access tokens signed by the same key so that they could access one rs. for example.

Current

                       ┌──────────┐                      
                       │          │                      
       Validates───────│ Client 1 │◀────┐                
         │             │          │     │                
         │             └──────────┘     │                
         │                              │                
         │                        access_token           
         ▼                              │                
   ┌──────────┐                         │     ┌─────────┐
   │          │                         │     │         │
   │    RS    │                         ├─────│ Kanidm  │
   │          │                         │     │         │
   └──────────┘                         │     └─────────┘
         ▲                              │                
                                  access_token           
         │                              │                
                       ┌──────────┐     │                
     Fails             │          │     │                
 signing key is ─ ─ ─ ─│ Client 2 │◀────┘                
  different to         │          │                      
client 1 signing       └──────────┘                      
      key                                                

Shared Key Domain

                    ┌──────────┐                      
                    │          │                      
    Validates───────│ Client 1 │◀────┐                
      │             │          │     │                
      │             └──────────┘     │                
      │                              │                
      │                        access_token           
      ▼                              │                
┌──────────┐                         │     ┌─────────┐
│          │                         │     │         │
│    RS    │                         ├─────│ Kanidm  │
│          │                         │     │         │
└──────────┘                         │     └─────────┘
      ▲                              │                
      │                        access_token           
      │                              │                
      │             ┌──────────┐     │                
      │             │          │     │                
    Validates───────│ Client 2 │◀────┘                
                    │          │                      
                    └──────────┘                      

The second example would work because both client 1 and 2 share a key domain so access-tokens are signed by the same key, allow tokens to be forwareded to an RS that trusts that same key domain.

bella-wmi commented 6 months ago

Thank you for your answer. To be honest I don't understand the concept of the key domains. What exactly is the validates arrow doing in your diagram?

My key question is: How can the resource server validate the access_token of a client (issued by kanidm) without knowing the client_secret?

Firstyear commented 6 months ago

They can either be enrolled as a client themself (within the same key domain), it can be oidc (with id_tokens), or I implement #2563 in the future (which I probably will do, I just have some other short term priorities).

bella-wmi commented 6 months ago

So if I understand correctly, when I create a oauth2 instances with kanidm system oauth2 create <name> <displayname> <origin> for my Client and do the same for a resource server. The resource server can not call the /introspect endpoint with his own credentials presenting the access token of the client, because they are not ins the same key domain?

bella-wmi commented 6 months ago

What I want to achieve is having a resource-server which can look into the access_tokens from the clients. But the clients could not look inside the token from other clients. So the resource_server has higher rights compared to the clients. Are we talking about the same thing when we are talking shared key domains?

Firstyear commented 6 months ago

So if I understand correctly, when I create a oauth2 instances with kanidm system oauth2 create <name> <displayname> <origin> for my Client and do the same for a resource server. The resource server can not call the /introspect endpoint with his own credentials presenting the access token of the client, because they are not ins the same key domain?

Today, that's correct.

What I want to achieve is having a resource-server which can look into the access_tokens from the clients. But the clients could not look inside the token from other clients. So the resource_server has higher rights compared to the clients. Are we talking about the same thing when we are talking shared key domains?

That's not how oauth2 works really, and I'm not sure that would be easily possible. This would require the token to be encrypted where the key can be derived between the client of the resource server and the kanidm server so that it can encrypt the signed token within so only the rs could decrypt it, but the other clients couldn't. It would be really annoying to do this I think. Oauth2 assumes you can just "yeet tokens around the place with reckless abandon" so either every client can read/validate them, or none can and they have to all ask the authorisation server for validity checks via the introspect api.

Generally in oauth2 clients are assumed to be trusted bearers of your tokens, and really even if they can't decrypt the token, they can always introspect it and see details about the user anyway, so all this potential "rs can decrypt but others can't" it's a whole lot of extra work if you have a client that you don't trust.

What's the actual situation you have here that you are trying to solve? It sounds like you're trying something really complex.

bella-wmi commented 6 months ago

What I want to achieve is having a resource-server which can look into the access_tokens from the clients. But the clients could not look inside the token from other clients. So the resource_server has higher rights compared to the clients. Are we talking about the same thing when we are talking shared key domains?

After sleeping a night, and rereading my comment here I think i made things more complicated as it should. In short terms what I want to achieve is calling the /introspect from the resource_server. So the shared key domain #2545 or access_token as JWT #2563 would solve this.

The whole thing about the rs can and the clients can't was a misunderstanding from my point where I mixed some stuff in my head, sorry.

Firstyear commented 6 months ago

Yeah, I think #2563 is probably easier to implement so I should do that soon, I don't see oauth2 shared key domains for a little while.