Unleash / unleash

Open-source feature management solution built for developers.
https://getunleash.io
Apache License 2.0
11.07k stars 693 forks source link

Improve multi-tenant security #8260

Open ricardopieper opened 1 week ago

ricardopieper commented 1 week ago

Describe the feature request

I wish the frontend SDKs were not be able to change their context at will. Or make it clear in the docs when you cannot use the client SDKs.

Background

Hello, we are studying whether we will adopt Unleash or not in our application.

We have a multi-tenant application, where tenants cannot see the data of another tenant in any way. This includes the feature toggle state.

We use the feature toggles both in our backend and frontend. The backend uses toggles to change the way we do certain things, while in the frontend we use it to conditionally render things. So we don't do anything crazy with it.

Our customers are very sensitive to security breaches (most of them are banking/finance) and we are under constant scrutiny from red teams, both our own and from our customers.

The docs suggest this is the way to segment users by tenant using the SDK:

const unleash = new UnleashClient({
    url: 'https://<your-unleash-instance>/api/frontend',
    clientKey: '<your-client-side-token>',
    appName: 'my-webapp',
});

//...

unleash.setContextField('tenantId', 'SomeTenantId');

In this case, client is in complete control of their own segmentation. We consider it to be a data breach if the user of tenant X can successfully check if another tenant has some feature enabled.

Our in-house feature toggle system today is very bad, but it is secure: the toggle state of one tenant never leaks to another tenant.

One way is to use an unguessable tenant ID but then it becomes more cumbersome to handle things in the UI.

The way I see it, the only way to use Unleash for us is to do the entire thing in our backend, and expose a route that the frontend clients can use to check that a certain feature toggle is enabled.

Am I missing something in the docs that addresses this or should we just do everything in the backend?

Solution suggestions

I didn't think too hard about it, so I don't know if they make sense or not.

Solution 1: Per-Tenant clientKey:

The SDK could be initialized with a different clientKey for each tenant. Using the unleash API, we could associate a client key with certain context properties that cannot be overwritten by the client. The app backend must return an appropriate clientKey for the frontend to initialize the client.

Solution 2: Predefined contexts identified by a key:

A separate unguessable key that you use to initialize the client. In Unleash, you define these keys and their context. Again the app backend is responsible for returning this key.

const unleash = new UnleashClient({ url: 'https:///api/frontend', clientKey: '', predefinedContextKey: await getUnleashPredefinedContextKey(), //does a request to the backend app appName: 'my-webapp', });

Maybe there are other solutions potentially leveraging Unleash Edge, but I can't think of one ATM.

ivarconr commented 1 week ago

Hi @ricardopieper,

thanks for reaching out. You did not mention which SDK you are leveraging, but based on your description I assume you are using a frontend SDK, and particular the JavaScript version.

First I want to stress that your entire frontend application is exposed as it already runs in the context of the user. You should not think of feature flags as a security / permission systems, and you should generally protect your data in the back-end.

The example on how to set the tenant context makes sense for a server-side application, where your control the code, and it is not exposed. I somewhat agree it to some degree is a bit unfortunate to expose the tenantId, if that allow you to identify customers, but again, a feature flag or knowing another tentantId should not enable access to actual customer data.

A simple solution right now would be to leverage ungessable tenantId thorugh a simple hashing function and define all possible tenantIds in Unleash as legal values on the context field.

This way you do not have to change your application logic, you can simply do: unleash.setContextField('tenantId', sha256('SomeTenantId'));

And in the UI you can select the tenant you are targeting, where you have pre-populated the possible value with a human readable display value, making it easier to work with.

I hope that helps!

(This being said, we have a discovery item in our roadmap to add a "contextEnricher" capability to edge, where you should be able to enrich the context on the edge side, e.g. automatically adding correct tenantId. Doing it server-side allows you to control the logic and avoid the need to trust the client).