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
|
Rather than adding a new path type. I think we can enriching the regular language matching mechanism we currently using in string matching (the I concretely propose the following syntax: These all remain analyzable because like the
The |
I don’t think we should use Taking a step back, maybe we should re-consider an MVP solution that handles most use cases: |
I agree with this. The |
|
I've been thinking about this today, and I think I can get behind it. I'm wondering about syntax though:
2 makes the most sense to me, but it feels rather unergonomic to use it and restrict it to a single character, when it generally isn't restricted in size. |
I’d stick with just single character separators to start with, not arbitrary strings. If we find that we absolutely need string separators later, we can allow them while remaining backward compatible. Since the separator string is a literal, CST -> AST or the validator can simply reject any literal with more than character. So we wouldn’t need a new type. We can explore other syntactic forms too (e.g., |
|
I like the idea of |
|
I think this is substantially similar to the We also need to nail down the multi-delimited pattern format. I see you propose Do we want Perhaps not necessary for a V1. But we might want to also consider if we want to allow matching each delimited section against a pattern rather than just wild-card. For example However, that is getting closer to full support for regular language patterns than the simple glob matching Cedar currently supports. |
I would like to confirm: Is the semantics of Assuming yes, I like this idea of using |
Yeah, let's break this down so we're on the same page:
|
Excellent! If you are aligned with this, would you be able to rework the RFC to make this the proposal and move the current proposal to "Alternatives" for posterity? I think this is a nice extension. It seems to be useful and it fits the language design well. |
I belive it's simply
I believe the intended semantics is Otherwise, that looks like a good change. |
so |
In @apg 's suggested semantics. The delimited glob operators match non-empty sequences. Where in the non-delimited glob operators match possibly empty sequences. So, |
|
FWIW, Slack's Secops use case also prefers the "like" mechanism over the original We like the double star |
Hmm. You're right. It definitely seems like you'd like to write it That could also provide an avenue for Thinking this through, I think the main benefit of the Path type is that the delimiter is eliminated entirely so these quirks don't exist. |
|
If you propose the Note that the |
Right. Now that I see it, I don't think we can ignore the position of the delimiter. It's too restrictive to always assume it's after especially if a segment can't be empty. |
Rendered
Checklist
text/SUMMARY.mdin numerical order