spicedb icon indicating copy to clipboard operation
spicedb copied to clipboard

Private scope for domain-namespaced definitions

Open merlante opened this issue 1 year ago • 4 comments

Problem Statement

In the case where authorization logic for multiple application domains are maintained in a single spicedb schema, it's possible and desirable in many cases for dependencies to develop across those domains, i.e. permissions in definition domainA/resource1 may depend on those in definition domainB/resource2. For example, in one domain, we may wish to authorize access to hosts, while in another domain, we may wish to leverage access to a host as part of granting permissions for viewing jobs on a host. (See example below.)

However, there is a need to ensure that the authorization logic in one domain does not become too tightly coupled to the logic in another over time. In particular, we would like to avoid cases where the permissions on a resource in one domain relies on a definition in another domain that should be considered a detail of the current implementation and not exposed to other domains for use in their authorization logic.

For example:

definition inventory/host_group {
     // currently most authz logic is written here at the host_group level and inherited by hosts, etc.

     permission inventory_view_hosts = ...
}

definition inventory/host {
      relation inventory/host_group: host_group

      permission view = host_group->inventory_view_hosts
}

// good – it is intended that host permissions be managed at the inventory/host level
definition jobrunner/run {
    relation inventory/host: host

    permission view = host->view
}

// bad -- host_group should remain scoped to the inventory namespace and have no external dependencies
definition jobrunner/run {
    relation inventory/host_group: host_group

    permission view = host_group->inventory_view_hosts
}

In this example, we don’t want schema definitions in jobrunner/run to relate to host_group, because host_group (for argument’s sake) is a legacy concept that we will someday remove from our application and schema. We would like the freedom to remove host_group without breaking any schema outside of inventory/. Meanwhile, inventory/host, and its permissions, are intended to remain the same into the future (even as other “internal” authorization logic changes behind it).

To avoid this problem, it seems desirable to preclude certain resources in a domain from being referred to in a permission or relation in a resource outside of that domain. That way we can have some control over which resources in a domain should be available for general use (public) and which should be considered an implementation detail (private).

Solution Brainstorm

Introduce a "private" specifier on the resource definition. Ensure that any schema which has a resource in one domain which attempts to refer to that resource from another domain in a relation or permission statement is an invalid schema. This logic can be embedded in the schema DSL itself and be validated at that level and need not necessarily imply any changes in the spicedb code.

e.g.

definition inventory/host_group **private** {
     // currently most authz logic is written here at the host_group level and inherited by hosts, etc.

     permission inventory_view_hosts = ...
}

definition inventory/host {
      relation inventory/host_group: host_group

      permission view = host_group->inventory_view_hosts
}

// the schema validation would fail on this definition because it refers to host_group in a different namespace.
definition jobrunner/run {
    relation inventory/host_group: host_group

    permission view = host_group->inventory_view_hosts
}

The addition of a specifier like “private” should ensure backwards compatibility with existing schemas.

merlante avatar Sep 13 '23 09:09 merlante

There has been some discussion (both internal and external) about whether some sort of scoping would be beneficial, especially with this proposal: https://github.com/authzed/spicedb/issues/1437

One big question immediately is whether it should be at the definition level or at the permission/relation level

josephschorr avatar Sep 13 '23 12:09 josephschorr

@merlante If we introduced syntax such as _ in front of a definition/relation/permission/caveat such that it was inaccessible outside of the schema "module" (see https://github.com/authzed/spicedb/issues/1437), would that be sufficient?

josephschorr avatar Sep 18 '23 18:09 josephschorr

One big question immediately is whether it should be at the definition level or at the permission/relation level

It feels cleaner and simpler to me to do it at the definition level. The main intent here is to discourage any kind of dependency on certain definitions, which should be considered "implementation detail" within a given domain.

If we introduced syntax such as _ in front of a definition/relation/permission/caveat such that it was inaccessible outside of the schema "module" (see https://github.com/authzed/spicedb/issues/1437), would that be sufficient?

I think that would fulfil the requirement, yes. My only slight concern is whether the intent would be clear from the underscore.

merlante avatar Sep 19 '23 13:09 merlante

The module proposal is very interesting! That kind of file separation is a totally logical next step, especially if we wanted to manage microservice subschemas with a gitops flow or similar, which we haven't explored a lot yet but would definitely be on the table if available.

As far as permission or relation- it would likely be both (though more on that in a moment.)

Hypothesis: a subschema's public contract is going to be a subset of its definitions, the ones that represent concrete things it exposes to other services and permissions that represent operations against those resources.

Hypothesis: a subschema may also include definitions and relations that represent implementation details like resource organization, relationships used to compute permissions (ex: the creator of a resource), and synthetic relations, all of which a service provider will want to be able to refactor without impacting other services.

Taking these for granted, being able to mark it at the relation/permission level would work but would mean that an entirely internal definition would need all of its members marked that way. Applying it only to definitions would mean any references to a private definition would need to be private implicitly, and there would be no way to hide synthetic relations and such.

Allowing both with the requirement that any references to private items are also private should cover all the bases. And being able to partition those modules into separate files would be awesome!

wscalf avatar Sep 19 '23 19:09 wscalf