Trusty: provenance library workspace (initial import)
This commit is contained in:
96
docs/scope-grammar.md
Normal file
96
docs/scope-grammar.md
Normal file
@@ -0,0 +1,96 @@
|
||||
# Scope grammar
|
||||
|
||||
The string format `trusty_policy.permits()` matches against. Two repos
|
||||
depend on it, so it is written down here rather than inferred from the
|
||||
implementation.
|
||||
|
||||
## The rule
|
||||
|
||||
```sql
|
||||
CREATE OR REPLACE FUNCTION trusty_policy.covers(scope TEXT, resource TEXT)
|
||||
RETURNS BOOLEAN
|
||||
LANGUAGE sql IMMUTABLE PARALLEL SAFE
|
||||
AS $fn$
|
||||
SELECT scope IS NOT NULL
|
||||
AND resource IS NOT NULL
|
||||
AND (resource = scope OR starts_with(resource, scope || '/'))
|
||||
$fn$
|
||||
```
|
||||
|
||||
A scope covers a resource when they are **equal**, or when the resource
|
||||
begins with the scope **followed by a `/`**.
|
||||
|
||||
That is the whole grammar. There are no wildcards, no globs, and no
|
||||
pattern syntax — deliberately.
|
||||
|
||||
## Shape
|
||||
|
||||
```
|
||||
<class>:<id>[/<segment>]*
|
||||
```
|
||||
|
||||
| part | example | rule |
|
||||
|---|---|---|
|
||||
| class | `board` | opaque to trusty |
|
||||
| id | `9f2` | opaque to trusty |
|
||||
| segment | `calendar`, `private` | separated by `/` |
|
||||
|
||||
Only the `/` is load-bearing. `<class>:<id>` is a convention the
|
||||
consumer chooses; trusty treats everything before the first `/` as one
|
||||
opaque string.
|
||||
|
||||
## What follows from it
|
||||
|
||||
**`/` is the only separator.** `board:9f2/calendar` covers
|
||||
`board:9f2/calendar/private`. It does **not** cover
|
||||
`board:9f2/calendar-private`, because the character after the scope is
|
||||
`-`, not `/`.
|
||||
|
||||
**A near-miss prefix does not match.** `board:o` does not cover
|
||||
`board:other/calendar`. Without the separator in the concatenation it
|
||||
would, which is why the separator is in there and not an afterthought.
|
||||
|
||||
**No wildcards.** A scope of `%` is a scope literally named `%`, and a
|
||||
scope containing `_` matches only a literal underscore. `starts_with` is
|
||||
used rather than `LIKE` precisely so an attacker-influenced scope cannot
|
||||
widen itself into a pattern.
|
||||
|
||||
**Case and Unicode are exact.** `Board:9f2` is a different scope from
|
||||
`board:9f2`. There is no normalisation, folding, or trimming anywhere in
|
||||
the path. Emit exactly what you mean to match.
|
||||
|
||||
**No trailing slash.** `board:9f2/calendar/` is a *different* scope from
|
||||
`board:9f2/calendar`, and covers `board:9f2/calendar//private` rather
|
||||
than anything useful. Do not emit one.
|
||||
|
||||
**Exclusions use the same rule.** An exclusion of
|
||||
`board:9f2/calendar/private` shuts that resource and everything beneath
|
||||
it, checked before coverage and across every live grant of the subject
|
||||
regardless of tier.
|
||||
|
||||
## What a producer must guarantee
|
||||
|
||||
Anything writing a `resource_scope` column, or issuing a grant, owes the
|
||||
following. Trusty cannot check them, because the parts are opaque to it:
|
||||
|
||||
1. Segments are emitted in the same shape both sides expect. An
|
||||
exclusion of `board:9f2/calendar/private` protects nothing if rows
|
||||
carry `board/9f2/calendar/private`.
|
||||
2. No segment contains a `/` of its own. A segment that does silently
|
||||
becomes two levels, and a grant one level up now covers it.
|
||||
3. Ids are not user-controlled free text. `board:<user-supplied>` lets
|
||||
someone name a board `9f2/calendar` and inherit a scope they were
|
||||
never granted.
|
||||
|
||||
Point 3 is the one that bites. If ids can contain `/`, the grammar has
|
||||
no boundary and coverage becomes forgeable.
|
||||
|
||||
## What trusty deliberately does not do
|
||||
|
||||
- Parse, validate or normalise a scope
|
||||
- Attach meaning to `class` or `id`
|
||||
- Enforce a maximum depth
|
||||
|
||||
The engine takes two opaque strings and asks whether one contains the
|
||||
other. Keeping it that narrow is what lets the consumer own the
|
||||
namespace — and is why the guarantees above are the consumer's to keep.
|
||||
Reference in New Issue
Block a user