Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: RFC 7523 - allow multiple subjects in trust relationships #2930

Closed
3 of 6 tasks
jagobagascon opened this issue Jan 10, 2022 · 9 comments
Closed
3 of 6 tasks

feat: RFC 7523 - allow multiple subjects in trust relationships #2930

jagobagascon opened this issue Jan 10, 2022 · 9 comments
Labels
feat New feature or request.

Comments

@jagobagascon
Copy link
Contributor

Preflight checklist

Describe your problem

The current implementation of the RFC requires a trust relationship to exist between the JWT issuer and the authorised subject. This means that a JWT issuer that needs to authorise many users requires a trust relationship for each of them to exist in Hydra.

I understand that in some environments the way this is implemented adds an additional layer of security: you may not have control over the JWT issuer, and this lets you limit who can be authorised by it. But in others (when the JWT issuer is part of your own infrastructure) it just adds a dependency between the issuer and Hydra and doesn't add much value in terms of security.

Our problem is that our JWT issuer does not have access to Hydra's admin backend.

Describe your ideal solution

We want a way to define trust relationships that allow the authorisation of many subjects, either by allowing any subject or only subjects under a certain domain (*@mydomain.com) to be authorised.

This trust relationships are out of the scope of the RFC, so adding this would have no impact on RFC compliance.

Workarounds or alternatives

I guess that exposing the Hydra backend to the internet (with some kind of security of course) to create a trust relationship before issuing a new JWT token would "fix" our problem.

But there's no way we are doing this. It would create a bunch of new problems, and a new attack vector to our infrastructure.

Version

master branch (The RFC 7523 feature is yet to be released)

Additional Context

There was some discussion about this topic in #2229 (comment).

There it was proposed to use the anonymous keyword as a subject and make it a required parameter. And I kinda like that solution, but I'm afraid it could cause problems on systems where anonymous is a valid user/subject name. So I think it should just be a flag on grant creation and just keep it as a null value in the database.

Pinging @Xopek as he is the one who implemented the RFC, in case he wants to share some thoughts about this.

@jagobagascon jagobagascon added the feat New feature or request. label Jan 10, 2022
@aeneasr
Copy link
Member

aeneasr commented Jan 11, 2022

Hello! I think this makes a lot of sense. It is not scalable to create trust relationships for every possible triple.

@Xopek it would be awesome to hear your input on this :)

@jagobagascon
Copy link
Contributor Author

I've been trying to find how other OAuth 2.0 Servers implement this feature but I haven't had any luck yet. The only solid example I could find is Google's service account's ability to act on behalf of any user in a Google Apps domain without consent from the user (this was already explained in the issue opened in fosite to implement this RFC ory/fosite#305 (comment)). This means that Google automatically applies a domain restriction, and I can't find any other example where the server lets the issuer sign tokens for any user without restriction.

I think I wouldn't feel confortable having an issuer with the power to impersonate any user (even though we have full control over it). Our needs are related to service-account authorisation, so we would be happy with just a domain restriction.

Just to make it clear, the restriction is not intended to protect the users, but to protect ourselves from our own mistakes and mitigate the damage in case the private key is leaked. Even with the current, more restrictive implementation, someone could just automatically setup trust relationships for every user and have an "unrestricted" issuer. We are looking for something in between.

What do you think about having some kind of pattern matching? I'm not a big fan of regular expressions, and it would require filtering the database result again in the code, but that way anyone could setup the trust relationship to their needs (i.e. .*@service-account.example.com).

Whatever we choose to do, I'm available to implement it myself.

@Xopek
Copy link
Contributor

Xopek commented Jan 20, 2022

Well, authorization flow using jwt is different from usual one in that manner, that we don't ask resource owner to grant us permission to represent him explicitly, but more like implicitly by showing some secret (signed jwt in our case), that only we and resource owner knows.

That's why Trust was created for each subject (where subject is resource owner identity). So that Resource Owner can have granular control over who can represent him. This is especially useful, when the issuer of the jwt is the client itself. I think you don't want to allow any client, that somehow got his hand on private key to represent any Resource Owner.

However in https://datatracker.ietf.org/doc/html/rfc7521 there is a second use case, when issuer of the jwt is not client itself, but Token Service. And there is a trust between Authorization Server (Hydra) and Token service. looks like it is your case, @jagobagascon. Well in that case i think it is ok to have some regex for subject, because we will have only one issuer in this case - Token service, and it will control who can request jwt, which will then be used to exchange it for token. But you should understand that in this case it is Token Service duty to control who has ability to request jwt. And jwt should be short lived and used only once to prevent attack, when someone managed to stole one jwt.

Anonymous aprouch i think is not good for your case, because anonymous subject should mean anonymous resource owner, and i don't think you want to give some permissions in your system for anonymous user (resource owner).

@jagobagascon
Copy link
Contributor Author

And there is a trust between Authorization Server (Hydra) and Token service

Yes, that's exactly our case. But I'm still sceptic about using regular expressions.

Compiling a regex is an expensive and slow task. Most applications will do it once at startup and keep it in memory forever. In our case expressions would be stored in the database, so we would have to compile them every time a JWT was received (unless we add some kind of cache). This could mean compiling a single regex, or a dozen of them depending on the issuer.

I think that in most cases having a simple domain restriction would be enough. If that's still too restrictive we could even allow some simple patterns like *.example.com to also include any subdomain (I think we could do this in a single database query).

That said, I would like to know what @aeneasr thinks before doing any attempt at implementing this.

@jagobagascon
Copy link
Contributor Author

I had some free time so I made an attempt at implementing a simple domain wide trust relationship: #2957.

@jagobagascon
Copy link
Contributor Author

We had a little chat with @aeneasr about this feature, so I'm going to try to summarize what we talked here.

I'm also going to share a small presentation we did that summarizes our problem, the steps we gave, and our proposal (Google Slides).

Background

Current JWT Assertion implementation requires a trust relationship to exist between the issuer and all the subjects it is allowed to represent, this makes it not scalable enough for some use cases, like an STS (secure token service). That's why we were trying to come up with some kind of restriction that was more flexible than that.

What did we try so far?

We made an attempt at adding a domain restriction, but that's only useful if the subject is an email field (which is discouraged). We also thought about using some kind of regular expression, and although this is much more flexible than using a domain, it's still not really helpful, because the subject field could contain any kind of user identifier (it could just be a number, and a regex would be useless in this case).

So, what are other implementations doing?

  • As I said earlier in this thread, Google Workspace allows it's service accounts to create tokens for any user that's part of the domain.
  • Curity Identity Server will trust any subject as long as the issuer is trusted. It just assumes that the party asserting the JWTs is very trustworthy and capable (source).

We propose more flexibility

Hydra is flexible by design, it implements a flexible user management by delegating user identification to an Identity Provider. This way developers can implement user management and login themselves and use any authentication mechanisms required by their use case (token-based 2FA, SMS 2FA, etc).

So we propose adding some of that flexibility to Hydra's implementation of the JWT assertion grant. We think Hydra could delegate the decision to trust a JWT token to an external service, just like it does for the login and consent flows. Here are some options:

Using an RPC call

Similar to how Envoy Proxy's external authorization filter works, Hydra could make an HTTP request that would answer with an OK or a NOT_OK.

This presents some problems like how to handle timeouts or errors.

Using a more powerful expression language

Another option could be using something like Google CEL. This would allow more complex validations over the incoming JWT (not only the subject).

WebAssembly (WASM)

WASM is a binary instruction format for a stack-based virtual machine. Wasm is designed as a portable compilation target for programming languages, enabling deployment on the web for client and server applications.

This would allow anyone to implement their own custom logic to validate the contents of the JWT (not the signature, that's Hydra's job). This includes a database access, an remote HTTP call or whatever the system needs.

 

 

That being said, these are long term proposals. All three options involve huge changes to Hydra's codebase and require a much deeper analysis.

Short term solution

So, in the short term we propose adding trusts relationships without a subject restriction. Other tools are doing this, and it should be pretty safe as long as it cannot be done by mistake.

Currently the issuer trust creation request requires the subject field to be present, and I would keep it that way to prevent creating an unrestricted issuer by mistake.

{
    "subject": "subject_id",
    "issuer": "issuer_id",
    // other options
}

So to create unrestricted issuers I would just add another field to the request:

{
    "unrestricted": true,
    "issuer": "issuer_id",
    // other options
}

One of both, the new field (unrestricted in my example) and the subject field would be required, but never both at them same time. I think this way Hydra would still be very safe by default, but it would allow use cases like ours to be implemented.

I will gladly implement this myself if @aeneasr agrees on the solution.

@aeneasr
Copy link
Member

aeneasr commented Feb 24, 2022

The short term solution sounds good to me! I am wondering whether we can find a bit clearer API naming? Maybe all_subjects or subject_unrestricted or something?

@jagobagascon
Copy link
Contributor Author

Mmmm, what about any_subject?

@glerchundi
Copy link
Contributor

Although I have always preference of adding action/verb to the API properties names whenever they're booleans (like allow_any_subject), I think all of them work! Perhaps always consistency over other params in Hydra matters most so I would follow that route. Choose which one you think fits better @aeneasr :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feat New feature or request.
Projects
None yet
Development

No branches or pull requests

4 participants