From 5f80d5161a1073bb0a2d703d63cc3a9e17387dac Mon Sep 17 00:00:00 2001 From: Charlie Murphy Date: Fri, 22 Aug 2025 14:22:30 -0400 Subject: [PATCH 1/3] Create 0101-nested-namespaces.md Signed-off-by: Charlie Murphy --- text/0101-nested-namespaces.md | 253 +++++++++++++++++++++++++++++++++ 1 file changed, 253 insertions(+) create mode 100644 text/0101-nested-namespaces.md diff --git a/text/0101-nested-namespaces.md b/text/0101-nested-namespaces.md new file mode 100644 index 00000000..17e57500 --- /dev/null +++ b/text/0101-nested-namespaces.md @@ -0,0 +1,253 @@ +# Allow Nested Namespaced in Cedar Schemas + +## Related issues and PRs + +- Reference Issues: +- Implementation PR(s): + +## Timeline + +- Started: 2025-08-22 +- Accepted: TBD +- Stabilized: TBD + +## Summary + +As of today, Cedar does not allow for defining nested namespaces. Instead, a schema writer must define fully qualified namespaces at the top-level of a schema file. At times this can be unintuitive and make it difficult to write schemas with proper name hygiene. + +## Basic example + +Suppose, we want to write a Schema for defining entity's and types that are grouped in a hierarchical structure (e.g., representing components of an organization). Consider the below example schema that defines entity types for AWS's and Google's identity and storage services. + +``` +namespace aws { + namespace iam { + entity User { + department: String, + role: String, + clearanceLevel: Long + } + entity Group { + groupType: String, + maxClearance: Long + } + } + + namespace s3 { + entity Bucket { + region: String, + sensitivity: Long + } + + entity Object { + type: String, + owner: iam::User + } + } +} + +namespace google { + namespace identity { + entity User { + email: String, + organizationId: String, + } + entity ServiceAccount { + displayName: String, + projectId: String, + } + } + + namespace storage { + entity Bucket { + projectId: String, + location: String, + storageCLass: String, + } + entity Object { + contentType: String, + owner: identity::User, + } + } +} +``` + +Note, that in today, the above schema is not possible due to nested namespaces. Instead, one must write the above schema as: + +``` +namespace aws::iam { + entity User { + department: String, + role: String, + clearanceLevel: Long + } + entity Group { + groupType: String, + maxClearance: Long + } +} + +namespace aws::s3 { + entity Bucket { + region: String, + sensitivity: Long + } + + entity Object { + type: String, + owner: aws::iam::User + } +} + +namespace google::identity { + entity User { + email: String, + organizationId: String, + } + entity ServiceAccount { + displayName: String, + projectId: String, + } +} + +namespace google::storage { + entity Bucket { + projectId: String, + location: String, + storageCLass: String, + } + entity Object { + contentType: String, + owner: google::identity::User, + } +} +``` + +While the current namespaces make it possible to distinguish between the similarly named `aws::iam::User` and `google::identity::User` and `aws::s3::Bucket` and `google::storage::Bucket`. It is unintuitive that the sub-namespaces cannot be nested in a single namespaces for `aws` and `google`, respectively and similarly that one cannot refer to `iam::User` inside `aws::s3` without fully qualifying the name as `aws::iam::User`. + +## Motivation + +In authorization, many types are naturally structured in a hierarchy (e.g., why we support defining hierarchies of identities). As such, it would be more intuitive for users to be allowed to define hierarchical namespaces that are explicitly nested that allow referencing those related resources via relative paths. Programmers naturally (and I think rightfully), expect to be able to define namespaces and types in a structured way. + +## Detailed design + +This RFC proposes updating the parser and name resolution algorithm for Cedar Schemas to enable nested namespaces with relative namespace resolution (for nested namespaces). Consider the example from above (with AWS and Google identity and storage entity types). This RFC proposes that both schemas are accepted and once parsed result in equal Schemas. + +### Updated Schema Syntax + +This RFC proposes that a Schema is a sequence of `Decl`s, a namespace is a namespace declaration containing a sequence of `Decls` and a `Decl` may now be a `Namespace`. This is opposed to the current grammar in which a Schema is a sequence of `Namespaces` which is either a namespace declaration containing a sequence of `Decl`s or a `Decl` without any surrounding namespace (i.e., within the root level namespace). + +The proposed grammar will allow for both nested namespaces (new) and fully qualified namespaces at the top level (current). + +``` +Annotation := '@' IDENT '(' STR ')' +Annotations := {Annotations} +Schema := {Decl} +Decl := Entity | Action | TypeDecl | Namespace +Namespace := Annotations 'namespace' Path '{' {Decl} '}' +Entity := Annotations 'entity' Idents ['in' EntOrTyps] [['='] RecType] ['tags' Type] ';' | Annotations 'entity' Idents 'enum' '[' STR+ ']' ';' +Action := Annotations 'action' Names ['in' RefOrRefs] [AppliesTo]';' +TypeDecl := Annotations 'type' TYPENAME '=' Type ';' +Type := Path | SetType | RecType +EntType := Path +SetType := 'Set' '<' Type '>' +RecType := '{' [AttrDecls] '}' +AttrDecls := Annotations Name ['?'] ':' Type [',' | ',' AttrDecls] +AppliesTo := 'appliesTo' '{' AppDecls '}' +AppDecls := ('principal' | 'resource') ':' EntOrTyps [',' | ',' AppDecls] + | 'context' ':' RecType [',' | ',' AppDecls] +Path := IDENT {'::' IDENT} +Ref := Path '::' STR | Name +RefOrRefs := Ref | '[' [RefOrRefs] ']' +EntTypes := Path {',' Path} +EntOrTyps := EntType | '[' [EntTypes] ']' +Name := IDENT | STR +Names := Name {',' Name} +Idents := IDENT {',' IDENT} + +IDENT := ['_''a'-'z''A'-'Z']['_''a'-'z''A'-'Z''0'-'9']* +TYPENAME := IDENT - RESERVED +STR := Fully-escaped Unicode surrounded by '"'s +PRIMTYPE := 'Long' | 'String' | 'Bool' +WHITESPC := Unicode whitespace +COMMENT := '//' ~NEWLINE* NEWLINE +RESERVED := 'Bool' | 'Boolean' | 'Entity' | 'Extension' | 'Long' | 'Record' | 'Set' | 'String' +``` + + +### Why relative name resolution for only structurally nested namespaces and not any namespace with the same path prefix? + +Consider the schema: + +``` +namespace Bar { + entity Thing; // (1) +} + +namespace Foo::Bar { + entity Thing; // (2) +} + +namespace Foo { + type MyThing = Bar::Thing; +} +``` + +Today, this is a currently valid schema. In which `Foo::MyThing` is a common type referring to `Bar::Thing` (definition 1). If we were to do relative name resolution based on namespaces with equal path prefixes, then `Foo::MyThing` would instead be a common type referring to `Foo::Bar::Thing` (definition 2), which would be backwards compatible. + +However, this RFC proposes that for nested namespaces, most users would expect definition 2 would be used. This RFC proposes that for structurally nested namespaces, definitions defined within the namespace declaration shadow namespaces outside of the namespace declaration. + +``` +namespace Bar { + entity Thing; // (1) +} + +namespace Foo { + namespace Bar { + entity Thing; // (2) + } + type MyThing = Bar::Thing; +} +``` + +Similarly, for the below schema, this RFC proposes that `Foo::Bar::Baz::MyUser` be a common type that references definition 2, because it's nested namespace scope is the closest enclosing scope to the common type declaration. + +``` +namespace Foo { + entity User; // (1) + namespace Bar { + entity User; // (2) + + namespace Baz { + type MyUser = User; + } + } +``` + +This is the bulk of the RFC. Explain the design in enough detail for somebody familiar with Cedar to understand, and for somebody familiar with the implementation to implement. This should get into specifics and corner-cases, and include examples of how the feature is used. Any new terminology should be defined here. + +### Name Resolution Algorithm + +Below is some pythonic pseudo-code for determining name resolution (simplified to only concern the portions of name resolution that are due to nested namespace, e.g., not dealing with specifics of common types, cedar_types, etc.). + +The algorithm is: first check the current namespace for the name. If it's not found in the current namespace check for any containing namespaces (note, a namespace only has a parent if it is a nested namespace), and otherwise search the global namespace for the name's definition. + +``` +resolve(name, this_scope, global_decls): + if name in this_scope: + return this_scope[name] + else if this_scope.has_parent(): + return resolve(name, this_scope.parent(), global_decls): + else: + return global_decls[name] +``` + +## Drawbacks + +While this change is likely to improve how one writes schemas, we should support nested schemas in a backwards compatible way. This means that, how we perform name resolution may be tricky in order to support backwards compatibility. This may either (1) limit how much users can exploit nested namespaces to use relative namespace identifies when defining entities and types or (2) increase the complexity of name resolution which may slow down parsing of Cedar Schemas. + +## Alternatives + +The primary alternative is not accepting this rfc and instead keeping the status quo. I believe the current status quo is a worse user experience when writing a Cedar schema but would not encounter the drawbacks stated above. + +An alternative to defaulting to use names (resolving to the name within the closest containing namespace), we could instead add explicit path identifiers for the current or containing scopes, e.g., `:this::User` and `:super::User` to help disambiguate relative versus global paths. From 98c3538a062689003f45f786948b12925d3e1b8e Mon Sep 17 00:00:00 2001 From: Charlie Murphy Date: Mon, 8 Sep 2025 14:03:18 -0400 Subject: [PATCH 2/3] Update 0101-nested-namespaces.md --- text/0101-nested-namespaces.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0101-nested-namespaces.md b/text/0101-nested-namespaces.md index 17e57500..dafe8b05 100644 --- a/text/0101-nested-namespaces.md +++ b/text/0101-nested-namespaces.md @@ -1,4 +1,4 @@ -# Allow Nested Namespaced in Cedar Schemas +# Allow Nested Namespaces in Cedar Schemas ## Related issues and PRs From 8d341e0b0e7925809d45ef3baf0323503aca5c2f Mon Sep 17 00:00:00 2001 From: Charlie Murphy Date: Mon, 8 Sep 2025 14:10:38 -0400 Subject: [PATCH 3/3] Update text/0101-nested-namespaces.md Co-authored-by: John Kastner <130772734+john-h-kastner-aws@users.noreply.github.com> --- text/0101-nested-namespaces.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/text/0101-nested-namespaces.md b/text/0101-nested-namespaces.md index dafe8b05..c3c8ccd4 100644 --- a/text/0101-nested-namespaces.md +++ b/text/0101-nested-namespaces.md @@ -224,8 +224,6 @@ namespace Foo { } ``` -This is the bulk of the RFC. Explain the design in enough detail for somebody familiar with Cedar to understand, and for somebody familiar with the implementation to implement. This should get into specifics and corner-cases, and include examples of how the feature is used. Any new terminology should be defined here. - ### Name Resolution Algorithm Below is some pythonic pseudo-code for determining name resolution (simplified to only concern the portions of name resolution that are due to nested namespace, e.g., not dealing with specifics of common types, cedar_types, etc.).