Propose a path extension type#115
Conversation
- Derived from discussion on @cedar-policy/rfcs cedar-policy#103 - path(..., ..., ...) becomes container to call .matches and .prefixMatches against Signed-off-by: Andrew Gwozdziewycz <andrew.gwozdziewycz@docker.com>
f4f7c6b to
2f41532
Compare
patjakdev
left a comment
There was a problem hiding this comment.
I wonder about the feasibility of generalizing the proposed path type to an ordered list type, where path becomes just a list[string].
The biggest wart that I think can think of is that list[string] would be the only type to allow for the special wildcard syntax when doing matching. A new any keyword could potentially be added to allow for wildcards in lists of other types.
|
|
||
| ## Summary | ||
|
|
||
| This RFC proposes a `path` extension type for Cedar. A path represents a variable-length, ordered sequence of strings — for example, the components of a filesystem path, S3 object key, URL path, MQTT topic, or reversed hostname. The `path` type exposes two matching methods, `.matches()` and `.prefixMatches()`, whose arguments must be string literals. This restriction keeps Cedar's SMT-based policy analysis decidable and efficient. String splitting and delimiter handling are intentionally out of scope; path values are constructed from pre-parsed components supplied by the application. |
There was a problem hiding this comment.
I might propose prefixLike instead of prefixMatches for symmetry with the like operator.
You might also consider a prefixEquals for situations where * should be treated as a literal asterisk rather than a wildcard without the need to escape it.
There was a problem hiding this comment.
I'm not sure about the Like vs Matches. I think Matches might work better given its relationship to structual pattern matching, though, you're right about the name symmetry--prefixLike might be more ergonomic. Open to other feedback here!
|
|
||
| #### `.matches(pattern1, ..., patternN)` | ||
|
|
||
| Returns `true` if the path has **exactly** `N` components and each matches the corresponding pattern: |
There was a problem hiding this comment.
Presumably, this method errors if a non-string is passed as an argument.
There was a problem hiding this comment.
Yeah. That would make sense! I'll have to update this.
| path(resource.bucket, context.tenant) // dynamic components | ||
| ``` | ||
|
|
||
| Arguments may be any string-typed expression — they are not required to be literals. Strict validation requires each argument to have type `string`. |
There was a problem hiding this comment.
Interesting. This might be the first extension type constructor that doesn't require a string literal for strict validation since it's not possible to pass an invalid value.
|
|
||
| ### Schema | ||
|
|
||
| ```json |
There was a problem hiding this comment.
Include the Cedar format as well?
|
|
||
| Records with positional fields work for paths whose length is fixed by the schema. They fail for variable-length paths — S3 keys, URL paths, MQTT topics — which is exactly what this RFC targets. | ||
|
|
||
| ### Alternative C: String attributes with `like` |
There was a problem hiding this comment.
Isn't this the same thing as Alternative F? I suppose alternatives A and B are also the same thing as Alternative F.
|
|
||
| - **`.get()` inclusion.** Should element access be included here or deferred to a follow-on? The primary value of `path` comes from `.matches()` and `.prefixMatches()`; `.get()` adds convenience but also implementation and specification surface area. | ||
|
|
||
| - **Wildcard semantics.** Pattern matching follows Cedar's `like` semantics: `*` matches zero or more characters within a single component and does not cross component boundaries. This means `path("foo", "bar").matches("*")` is `false` (length 2, not 1). The interaction between zero-length matches and the hostname use case (where `*.github.com` should require a non-empty subdomain) should be confirmed. |
There was a problem hiding this comment.
I don't quite understand what this is getting at.
There was a problem hiding this comment.
Basically, matches("*") is strict on the number of components. In the example above path("foo", "bar").matches("*", "bar") is fine because it has two components. The variant prefixMatches supports the variable number of components.
|
|
||
| - **Wildcard semantics.** Pattern matching follows Cedar's `like` semantics: `*` matches zero or more characters within a single component and does not cross component boundaries. This means `path("foo", "bar").matches("*")` is `false` (length 2, not 1). The interaction between zero-length matches and the hostname use case (where `*.github.com` should require a non-empty subdomain) should be confirmed. | ||
|
|
||
| - **Empty path behavior.** Should `path()` — a zero-component path — be a valid value? `path().matches()` and `path().prefixMatches()` would both return `true`, which seems correct, but edge cases in entity data deserve consideration. |
There was a problem hiding this comment.
Seems fine to me.
If we were to generalize path into list, though, it would be a problem because the type would be unknown.
I think this is something @emina would have to look at. I simply based this off of this comment #103 (comment). It seems as though general lists would be analyzable if they worked like the proposal (only integer literals for accessing elements, no length). |
Signed-off-by: Andrew Gwozdziewycz <andrew.gwozdziewycz@docker.com>
c250ee9 to
1ba6996
Compare
Rendered
Checklist
text/SUMMARY.mdin numerical order