Skip to content

[server] Perform authorization checks for Orgs against spicedb #16207

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

Merged
merged 1 commit into from
Feb 6, 2023

Conversation

easyCZ
Copy link
Member

@easyCZ easyCZ commented Feb 3, 2023

Description

Behind an experiment, performs authorization checks for Organizaitons against SpiceDB, and reports the outcome. Whether the outcome matches, or not, we always return the result of the existing authorization check.

Effectively, this change allows us to being checking perms against spicedb, without affecting the system, only collecting data.

We do not yet create any relationships against spicedb. As a result, all of these checks will return "NO_PERMISSION" initially. That's desired. We want to have the checks in place, and incrementally start adding these relationships, either from a batch job, or when resources are actually created. This will be done in subsequent PR(s).

Related Issue(s)

How to test

  1. Preview
  2. Sign in, create organization, interact with organization in any way
  3. There is no change in behaviour
  4. There are logs indicating the permission checks are happening
  5. Metrics are reported

Release Notes

NONE

Documentation

Build Options:

  • /werft with-github-actions
    Experimental feature to run the build with GitHub Actions (and not in Werft).
  • leeway-no-cache
    leeway-target=components:all
  • /werft no-test
    Run Leeway with --dont-test
  • /werft publish-to-npm

Preview Environment Options:

  • /werft with-local-preview
    If enabled this will build install/preview
  • /werft with-preview
  • /werft with-large-vm
  • /werft with-gce-vm
    If enabled this will create the environment on GCE infra
  • /werft with-integration-tests=all
    Valid options are all, workspace, webapp, ide, jetbrains, vscode, ssh

@easyCZ easyCZ changed the base branch from main to mp/spicedb-server-client February 5, 2023 20:54
@easyCZ easyCZ force-pushed the mp/spicedb-server-checks branch from dc7717e to ff08aae Compare February 5, 2023 21:08
@easyCZ easyCZ changed the title [server] Spicedb server checks [server] Perform authorization checks for Orgs against spicedb Feb 5, 2023
@werft-gitpod-dev-com
Copy link

started the job as gitpod-build-mp-spicedb-server-checks.6 because the annotations in the pull request description changed
(with .werft/ from main)

@easyCZ
Copy link
Member Author

easyCZ commented Feb 5, 2023

/hold for dependency on #16197

@easyCZ easyCZ marked this pull request as ready for review February 5, 2023 21:14
@easyCZ easyCZ requested a review from a team February 5, 2023 21:14
@github-actions github-actions bot added the team: webapp Issue belongs to the WebApp team label Feb 5, 2023
Comment on lines +37 to +41
export const ReadOrganizationMetadata = check("user", "organization_metadata_read", "organization");
export const WriteOrganizationMetadata = check("user", "organization_metadata_write", "organization");

export const ReadOrganizationMembers = check("user", "organization_members_read", "organization");
export const WriteOrganizationMembers = check("user", "organization_members_write", "organization");
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are declarative permission checks. The idea is to be able to collate all of these in a single place, and be able to trace their usage.

Comment on lines +2131 to +2156
protected async guardOrganizationOperationWithCentralizedPerms(
orgId: string,
op: OrganizationOperation,
): Promise<CheckResult> {
const user = this.checkUser();

const members = await this.teamDB.findMembersByTeam(team.id);
await this.guardAccess({ kind: "team", subject: team, members }, op);
return { team, members };
switch (op) {
case "org_metadata_read":
return await this.authorizer.check(ReadOrganizationMetadata(user.id, orgId));
case "org_metadata_write":
return await this.authorizer.check(WriteOrganizationMetadata(user.id, orgId));

case "org_members_read":
return await this.authorizer.check(ReadOrganizationMembers(user.id, orgId));
case "org_members_write":
return await this.authorizer.check(WriteOrganizationMembers(user.id, orgId));

default:
return NotPermitted;
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This acts as an adapter between the "fine grained ops" defined as strings, and the actual checks. Once we have fully migrated, this adapter won't exist, but it makes the existing code a bit easier to evolve, and reason about.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we going to pass the check constants instead fo the strings? Would be great to move this into the authorization folder as well.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Eventually will go away, once we migrate but for now, they are useful as a way of adapting the behaviour.

@@ -2037,7 +2046,7 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
protected async guardTeamOperation(
teamId: string,
op: ResourceAccessOp,
fineGrainedOps: OrganizationOperation[],
fineGrainedOp: OrganizationOperation,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Originally I thought we'd have a list of these, but I've since changed my mind. We should only reference a single operation, which in turn may require multiple different perms (like Inviting team members will require organizaiton_metadata_read and organization_members_write)

@easyCZ easyCZ force-pushed the mp/spicedb-server-checks branch from ff08aae to c0fe7d6 Compare February 6, 2023 07:24
Copy link
Member

@svenefftinge svenefftinge left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really like where this is going!

@@ -2037,7 +2052,7 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
protected async guardTeamOperation(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be great to move this logic out of this class into it's own one.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, eventually. For now, I'm trying to incorporate it to limit the amount of changes. The nice part of the PermissionChecker interface is that we could effectively implement the current behaviour (with db) such that it conforms to the PermissionChecker interface, and then split on the two different implementations behind a feature flag.

const team = await this.teamDB.findTeamById(teamId);
if (!team) {
// We return Permission Denied because we don't want to leak the existence, or not of the Organization.
throw new ResponseError(ErrorCodes.PERMISSION_DENIED, `No access to Organization ID: ${teamId}`);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't seem to be a change from this PR, but I think it is more common to return a not-found error code in such cases (including no permissions but resource exists).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it's not a change from this PR (here, it's just thunked).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks commented there


export const PermissionChecker = Symbol("PermissionChecker");

export interface PermissionChecker {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the purpose of introducing an interface for this? Also the naming pattern is a little unusal, as we'd uzse the same name for bot interface and implmentations but just add a word about the implementation to it. E.g. PermissionCheckerSpiceDBImpl but then again I'm not sure we need an interface at all, especially if the signature contains types from authzed.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the guard layer of APIs should only use a PermissionChecker. Once that check passes, and we create a resource, we'll want to use a PermissionGranter. They can both be implemented by the same class (Authorizer), but the type signatures should be constrained. The goal is to use composition, rather than exposing everything. This makes it easier to actually write stub implementations for tests (if we had any..).

The type does in fact rely on the spicedb type. Not ideal, but at this stage I wanted to reduce re-definition of types. If we needed to abstract, it would be relatively easy to swap the argument type for a custom one.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you, makes sense 👍

Comment on lines +2131 to +2156
protected async guardOrganizationOperationWithCentralizedPerms(
orgId: string,
op: OrganizationOperation,
): Promise<CheckResult> {
const user = this.checkUser();

const members = await this.teamDB.findMembersByTeam(team.id);
await this.guardAccess({ kind: "team", subject: team, members }, op);
return { team, members };
switch (op) {
case "org_metadata_read":
return await this.authorizer.check(ReadOrganizationMetadata(user.id, orgId));
case "org_metadata_write":
return await this.authorizer.check(WriteOrganizationMetadata(user.id, orgId));

case "org_members_read":
return await this.authorizer.check(ReadOrganizationMembers(user.id, orgId));
case "org_members_write":
return await this.authorizer.check(WriteOrganizationMembers(user.id, orgId));

default:
return NotPermitted;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we going to pass the check constants instead fo the strings? Would be great to move this into the authorization folder as well.

@easyCZ
Copy link
Member Author

easyCZ commented Feb 6, 2023

/werft run

👍 started the job as gitpod-build-mp-spicedb-server-checks.8
(with .werft/ from main)

Base automatically changed from mp/spicedb-server-client to main February 6, 2023 09:14
@easyCZ easyCZ force-pushed the mp/spicedb-server-checks branch from c0fe7d6 to ceb0c9b Compare February 6, 2023 09:18
@easyCZ
Copy link
Member Author

easyCZ commented Feb 6, 2023

/werft run with-clean-slate-deployment=true

👍 started the job as gitpod-build-mp-spicedb-server-checks.10
(with .werft/ from main)

@easyCZ
Copy link
Member Author

easyCZ commented Feb 6, 2023

/unhold

@roboquat roboquat merged commit 8233dc7 into main Feb 6, 2023
@roboquat roboquat deleted the mp/spicedb-server-checks branch February 6, 2023 09:59
@roboquat roboquat added deployed: webapp Meta team change is running in production deployed Change is completely running in production labels Feb 6, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
deployed: webapp Meta team change is running in production deployed Change is completely running in production release-note-none size/XL team: webapp Issue belongs to the WebApp team
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants