quickwit-oss / quickwit

Cloud-native search engine for observability. An open-source alternative to Datadog, Elasticsearch, Loki, and Tempo.
https://quickwit.io
Other
8.31k stars 339 forks source link

RBAC support and authorization in all gRPC [WIP] #5533

Open fulmicoton opened 3 weeks ago

fulmicoton commented 3 weeks ago

RBAC support

Authentication

Quickwit will maintain its own model of user, roles and permissions its the metastore.

For the first iteration, we will only allow LDAP authentication. The user experience will be as follows.

When accessing the quickwit ui without being logged in, the user will be redirected to a login form. After submitting their credential, the credential will go through the front-end and be forwarded to a new quickwit-auth service.

This service will in turn connect to LDAP to authenticate the user. The username will be completed with a configured baseDN. If successful, quickwit-auth will fetch the list of roles associated to the user in the metastore.

If no user is found, a new user will be automatically created, with a default role, and associated to the user DN.

Quickwit auth will then generate a biscuit token with at least the following information:

It may also include a partial list of permissions. Some permissions are tied to a specific resource (e.g. role marketing is allowed to read the index named access_log). The number of such index could be very large, so we limit the number of resources that are included in this biscuit.

This biscuit token is then returned back to the front-end as a cookie.

quickwit-auth is in charge of authentication. It is the only service that has the authority to create such a root token.

Token renewal

When the backend attempts to access a resource (e.g. read an index) that is not already listed in the biscuit, a biscuit renewal request is sent to the quickwit-auth service. The quickwit-auth service will then return a new biscuit with all of the permissions granted to the user for the specific resource.

Roles and permissions

Roles are simply strings identifying groups of users. Each role is associated to a set of permissions. The permissions of a user is the union of the permissions of all the roles they have.

A new user always receive the default role. A request missing any authorization token will receive the anonymous role.

Permissions may be tied to a specific resource:

Unfortunately, the cardinality of resources (thousands) prevent us from adding all existing permissions into the biscuit. We can however safely assume that the number of roles per user (dozens) is much smaller and can fit in a biscuit.

Authorization

A biscuit token is added to all gRPC. On the client side, the token is attenuated. We add a TTL of 1 minute, and its scope is reduced to the need of the current request.

On the server side, we check that the token is sufficient to perform the request.

Datalog

Our biscuit token is a datalog program.

It starts with an authority block. This is the part that is emitted by the auth server.

// This is what comes from our biscuit stored as a cookie in the browser.
//
// It says what the user is and what role it is associated too.

user("fulmicoton");
member("developer");
member("admin");

// The TTL
check if time($time), $time <= 2024-12-02T08:00:00Z;

Before emitting a grpc, grpc clients attenuate this token and pass it with the request as a bearer token, as a grpc metadata (in the "authority" header of the associated http/2 request).

The attenuation block includes a more aggressive TTL, and some restriction on the allowed operation. In order to remove the burden of naming, we will name the operations after the grpc service methods. It is important that this includes all of the suboperations induced by the grpc.

check all grpc($grpc), ["RootSearch", "FetchDocs"].contains($grpc);
check if time($time), $time <= 2024-12-02T07:01:00Z;

Finally an authorizer will be in

// We list resources here in order to generate facts for the root role.
resource("index1");
resource("index2");

// This is simply gRPC method.
grpc("SearchRequest");

// These relevant role rights are loaded from the database.
role_right("developer", "index1", "read");
role_right("developer", "index2", "read");
role_right("root", "index2", "read");

operation("read");

// We generate the actual user role, by generating facts using a rule.
right($operation) <- role($role), role_right($role, $operation);
right($operation, $resource) <- role($role), role_right($role, $resource, $operation);
right($operation, $resource) <- role("root"), operation($operation), resource($resource);

// Finally we check that we have access to index1 and index2.
check if right("read", "index1");
check if right("read", "index2");

allow if true;
github-actions[bot] commented 3 weeks ago

On SSD:

Average search latency is 1.01x that of the reference (lower is better).
Ref run id: 4045, ref commit: 105aa7d146531358aa62e9b7058029bae622a2eb
Link

On GCS:

Average search latency is 0.932x that of the reference (lower is better).
Ref run id: 4046, ref commit: 105aa7d146531358aa62e9b7058029bae622a2eb
Link