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

[RFC][v3] Authorization Rules in V3 #10237

Open
wants to merge 5 commits into
base: master
Choose a base branch
from

Conversation

abhinav-hasura
Copy link
Member

Description

Rendered

@abhinav-hasura abhinav-hasura requested a review from a team as a code owner May 14, 2024 22:41
@abhinav-hasura abhinav-hasura added the k/rfc RFC for a new feature / process label May 14, 2024
```yaml
kind: TypePermissions
version: v2
definition:
Copy link
Contributor

Choose a reason for hiding this comment

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

What do rules like these mean for the generated GraphQL schema?

Will internal_order_notes always be visible, but we'll make it nullable?

Copy link
Contributor

Choose a reason for hiding this comment

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

(or would accessing a field that one is not allowed trigger an error instead of a null field?)

Copy link
Member Author

Choose a reason for hiding this comment

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

internal_order_notes will not be visible in the GraphQL schema if the x-hasura-role session variable is not set to admin in the introspection query.

(Note that there is a separate conversation going on around eliminating dynamic role based schemas altogether but for the purpose of this RFC, let's assume we still want the GraphQL schema to contain only what's accessible)


### Implementation

The above proposal has major implications for engine, the `lang-graphql` crate in particular. Currently, `lang-graphql` allows constructing a single graphql schema with information about all roles embedded into the schema and allows query validation / IR generation to happen in the context of a particular role only. However, with the above proposal, there is no notion of roles anymore. So, `lang-graphql` will need to move to an architecture where when trying to access a graphql field, instead of looking up an allowed set of roles, it can evaluate an arbitrary expression that evaluates to a boolean. The details of this architecture are out of scope for this RFC.
Copy link
Contributor

Choose a reason for hiding this comment

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

This presumably means losing the ability to generate a GraphQL schema (and thus generate types etc) for only the queries / types that a given role is allowed to access. This feels like quite a price to pay, are we satisfied that users don't need this?

Copy link
Member Author

Choose a reason for hiding this comment

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

No, we would still generate the GraphQL schema only according to what's accessible to a given set of session variables.

When an introspection query comes in, we would evaluate which fields/types are accessible based on the session variables associated with the request and generate a schema containing only those types/fields.

version: v1
definition:
typeName: User
roles:
Copy link
Contributor

Choose a reason for hiding this comment

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

I know this isn't the root concern of this RFC, but surely there are other ways to make such a permission less arduous to define, for instance?

  permissions:
    - roles:
      - developer_base
      - developer_with_gov_access
      - support_agent_base
      - support_agent_with_gov_access
      output:
        allowedFields: [id]
    - roles:
      - developer_with_pii_access
      - developer_with_pii_access_and_gov_access
      - support_agent_with_pii_access
      - support_agent_with_pii_access_and_gov_access
      output:
        allowedFields: [id, name, email]

Copy link
Member Author

Choose a reason for hiding this comment

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

Right, for type permissions itself, it's not as big a deal, but you would still need to model 8 different roles in your authn/authz system. And now imagine, another attribute like 'hippa_access' gets added, now I would have 16 roles to manage.

So, fundamentally the issue is that it's not always feasible to enumerate all possible authz states that can exist in my system since that can grow exponentially with each independent attribute in my system.

fieldName: is_gov
operator: _eq
value: literal: true
- role: support_agent_with_gov_access # Same for support_agent_with_pii_access_and_gov_access
Copy link
Contributor

Choose a reason for hiding this comment

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

If we're concerned about the usability of metadata, then it doesn't seem wild that eventually we're going to want to abstract these predicates too and refer to them by name.

.....
- role: support_agent_with_gov_access # Same for support_agent_with_pii_access_and_gov_access
  select:
    filter:
      relationship:
        name: tickets
        predicate: agent_predicate
---
kind: Predicate
version: v1
definition:
  name: agent_predicate
  fieldComparison:
    field: agent_id
    operator: _eq
    value: sessionVariable: x-hasura-agent-id

Copy link
Member Author

Choose a reason for hiding this comment

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

In a world where my rules are composable, the ability to reuse predicates isn't that important.
The fundamental problem that is being addressed is not the ability to reuse parts of my metadata, but not having to enumerate all possible authorization states in my system.

# Rule 2: Allow name and email if pii access is allowed
- allowFields: [name, email]
condition:
equals:
Copy link
Contributor

Choose a reason for hiding this comment

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

Have we considered the somewhat opposite design to this, where we use a role somewhat like a JWT claim.

For example, instead of inhabiting a single role, on a given request a user might have both the has-pii role AND the can-delete role.

Then instead of inlining conditions, we could have a top-level piece of metadata that works out which roles are applicable for a request using predicate conditions like this?

kind: RolePredicate
version: v1
definition:
  name: has-pii
  condition:
    equals:
      left: sessionVariable: x-hasura-has-pii-access
      right: literal: true

Feels like if repetition is a concern, this might be neater, and would allow us to buy back a few more static guarantees (ie, if a request has roles X and Y, the schema will be like this)

Copy link
Contributor

Choose a reason for hiding this comment

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

I sketched out what this might look like here: https://gist.github.com/danieljharvey/2718cf1a8fc340a5b21063bc5e843b36

Copy link
Member Author

Choose a reason for hiding this comment

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

We can still have static guarantees with the current design, i.e., if a request has session variables x-hasura-has-pii-access: true and x-hasura-role: developer, then the schema will be like this.

Also, there is a subtle difference between role predicates and independent rules w.r.t how type permissions and model permissions interact.

Eg: With independent rules:

- allow row 1 of model X, if condition1
- allow all rows of model X, if condition 2
- allow all fields of model X, if condition1
- allow field A of model X, if condition 2

If both condition1 and condition2 are true, i can see that all rows and fields will be accessible.

But, if I used role predicates:

Roles:
- role1 if condition1
- role2 if condition2

Permissions:
- role1 can access all fields of row 1 of model X
- role2 can access field A of all rows of model X

Now if both condition1 and condition2 are true, then I would expect to have access to field A of all rows, and all fields of row 1.
This sort of cell-level authorization we explicitly don't want to support.


Note, however, that cell-level authorization will not be supported by this system, since you define type (column) permissions and model (row) permissions independently.

3. **Are any changes required to the NDC protocol?**
Copy link
Contributor

Choose a reason for hiding this comment

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

But if row and column permissions are disjoint, would you not want to push down the cell-level predicates? Or are you proposing that we redact data on the engine side.

That would be simpler but result in more network overhead.

I think it'd be worth spelling this out here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
k/rfc RFC for a new feature / process
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants