openiddict/openiddict-core

Sharing the same signing key in a multi tenant environment

vpszi opened this issue · 7 comments

Confirm you've already contributed to this project or that you sponsor it

  • I confirm I'm a sponsor or a contributor

Version

3.1.1

Question

I read your advice on SO about using distinct signing-keys for different tenants:

The approach suggested by McGuire will work with OpenIddict (you can access the acr_values property via OpenIddictRequest.AcrValues) but it's not the recommended option (it's not ideal from a security perspective: since the issuer is the same for all the tenants, they end up sharing the same signing keys).

I’d like to deeper understand the reason why it is an issue when all tenants share the same signing key.
Cyberark explains the separation of signing-keys as follows:

Do not set a single OIDC Identity Provider issuer (often referred to as the Entity ID or "issuer") to serve multiple tenants (two or more) as the tenants end up sharing the same signing keys, and then there is no real native ability for the issuer to distinguish between tenants. As an alternative, we highly recommend running a single issuer per tenant to avoid such multi-tenancy security risk.

If I own and trust the OIDC Identity Provider, I guess there wouldn’t be a benefit in having separate signing keys. Can you agree?

Are there additional reasons why sharing signing-keys is not recommended? What are the potential security risks?

Hey @vpszi,

Thanks for sponsoring the project! Much appreciated ❤️

If I own and trust the OIDC Identity Provider, I guess there wouldn’t be a benefit in having separate signing keys. Can you agree?

There are actually two related - but separate concepts - at play here:

  • The provider identity (a.k.a issuer), which is returned by the authorization server as an issuer node in the OIDC discovery document and as an iss claim in JWT identity tokens. This value uniquely identifies an OIDC server, so if you decide to adopt a multitenant model for your authorization server, the same issuer value MUST NOT be shared between instances to help prevent confused deputy attacks (otherwise, a client or resource server might tricked into accepting identity or access tokens meant to be used with a different OIDC server instance/tenant). That's what Cyberark means when they say Do not set a single OIDC Identity Provider issuer (often referred to as the Entity ID or "issuer") to serve multiple tenants (two or more).

  • The encryption and signing credentials used to protect the tokens. Technically, nothing prevents reusing the same set of credentials across OIDC servers/tenants. But it's really something to avoid for at least two reasons:

    • If an attacker somehow manages to steal the encryption or signing keys used by a tenant, he'll be able to not only decrypt the tokens issued by that tenant, but also by any other tenant, which is far from ideal from a security perspective. Information theft isn't the only risk since the attacker can also forge counterfeited tokens that will be considered valid by all the resource servers trusting one of the OIDC server instances and do unauthorized operations via privilege escalation.

    • Some client or resource server implementations are known to either completely skip iss validation or use a relaxed validation logic (e.g some implementations support wildcard domains, so legit.domain.com and malicious.domain.com are considered valid, even if they point to completely separate instances). In this case, the only line of defense is the signing key used to sign the tokens: if you, as the OIDC server operator, decided to share the same signing key for multiple tenants, then there's a serious risk a client or resource server configured to trust tenant-a.domain.com will end up accepting tokens issued for tenant-b.domain.com (that may be operated by a malicious party).

Do you have any particular reason for wanting to use the same set of credentials? Any chance you could tell me more about your scenario?

Hope it was clear (it's definitely not easy stuff 😄)

Hi @kevinchalet

Thanks for your detailed reply. I will share some background information.

Our system looks as follows:

  • We have a deployment monolith that contains everything, identity provider, authorization server, user management etc.
  • All tenants communicate with the same application instance and they use the same domain.
  • The distinction between tenant a and tenant b depends on the tenant claim - not on a subdomain or any other url-segment.
  • Access to TenantA-Db is only granted if the current user has the claim tenant: A.

One could say that our authorization logic doesn't solely rely on oidc.
it's based on a higher level.

For me it looks like the argumentation assumes path-based tenant distinction.
Could it be that other rules apply for claim-based tenant distinction?

Regarding our monolithic deployment, i think that an attacker who manages to steal one tenants signing key would also be able to steal signing keys of the other tenants since they are stored in the same place. How would such an attack actually look like? An attacker would probably need access to the host system..?

Do you have any particular reason for wanting to use the same set of credentials?

Since open-iddict doesn't support multi tenancy out of the box there would be a lot of work to achieve this - especially since we share the same open-iddict code with multiple projects.
I want to see a clear benefit before extending the code.

Thanks for sharing these details, @vpszi!

It's not 100% clear to me whether the OIDC server is itself multitenant - with a different config' per tenant - or if it's only the user part that is multitenant. E.g, where do you store the OpenIddict entities? In a shared database or in tenant-specific databases? Are your client registrations shared between all the tenants or are they tenant-specific?

Regarding our monolithic deployment, i think that an attacker who manages to steal one tenants signing key would also be able to steal signing keys of the other tenants since they are stored in the same place. How would such an attack actually look like? An attacker would probably need access to the host system..?

In general yes, but it really depends on how you implemented things (e.g I had a case a few years ago where the team had implemented a scripting logic that allowed the tenant manager to customize the OpenIddictServerOptions instance using JS scripts: sadly, there was no check at all regarding what properties could be customized so it was very easy to extract the private signing key via OpenIddictServerOptions.SigningCredentnials and append it to the JWKS endpoint path so it would be leaked in the OIDC discovery document 🤣)

The OpenIddict entities are stored in the shared database as well as the client registrations (a.k.a. users).

The OpenIddict entities are stored in the shared database as well as the client registrations (a.k.a. users).

Ah, in this case, I wouldn't really call that a "multitenant" OIDC server (tho' it's multitenant-aware as it's able to resolve the user details from the right place using a custom "tenant" claim).

Multitenant OIDC servers are generally more involved: e.g they don't share any client registration between tenants, they typically allow you to customize the enabled flows/endpoints per instance and they will generally maintain different credentials per tenant. It's the model used by OrchardCore, whose OpenID module is based on OpenIddict.

Since you essentially have a single OIDC server instance, having a single set of credentials makes sense 😃

thanks a lot @kevinchalet
now everything is clear 😄

My pleasure, @vpszi! Thanks for sponsoring the project 😃