Documentation
¶
Overview ¶
The zanzigo-package provides building blocks for creating your own Zanzibar-esque authorization service.
Your start by defining an authorization model:
model, err := zanzigo.NewModel(zanzigo.ObjectMap{ "user": zanzigo.RelationMap{}, "group": zanzigo.RelationMap{ "member": zanzigo.Rule{}, }, "folder": zanzigo.RelationMap{ "owner": zanzigo.Rule{}, "editor": zanzigo.Rule{ InheritIf: "owner", }, "viewer": zanzigo.Rule{ InheritIf: "editor", }, }, "doc": zanzigo.RelationMap{ "parent": zanzigo.Rule{}, "owner": zanzigo.Rule{ InheritIf: "owner", OfType: "folder", WithRelation: "parent", }, "editor": zanzigo.AnyOf( zanzigo.Rule{InheritIf: "owner"}, zanzigo.Rule{ InheritIf: "editor", OfType: "folder", WithRelation: "parent", }, ), "viewer": zanzigo.AnyOf( zanzigo.Rule{InheritIf: "editor"}, zanzigo.Rule{ InheritIf: "viewer", OfType: "folder", WithRelation: "parent", }, ), }, })
With a storage-implementation available, tuples can be inserted (check whitepaper for notation or altenatively construct Tuple directly):
// We add user 'myuser' to the group 'mygroup' _ = storage.Write(ctx, zanzigo.TupleString("group:mygroup#member@user:myuser")) // The document 'mydoc' is in folder 'myfolder' _ = storage.Write(ctx, zanzigo.TupleString("doc:mydoc#parent@folder:myfolder")) // Members of group 'mygroup' are viewers of folder 'myfolder' _ = storage.Write(ctx, zanzigo.TupleString("folder:myfolder#viewer@group:mygroup#member"))
Using a Resolver permissions can be checked by traversing the tuples using the inferred rules of the authorization-model:
resolver, _ := zanzigo.NewResolver(model, storage, 16) // Based on the indirect permission through the group's permissions on the folder, // the following would return 'true': result, _ := resolver.Check(context.Background(), zanzigo.TupleString("doc:mydoc#viewer@user:myuser"))
For more examples, check the repository. You may find additional information in the README.
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ( // TODO: doc ErrTypeUnknown = errors.New("Unknown type used in tuple") // TODO: doc ErrRelationUnknown = errors.New("Unknown relation used in tuple") )
var EmptyTuple = Tuple{}
var ( // Returned by Storage-implementation for example if a given Read did not return a result. ErrNotFound = errors.New("not found") )
Functions ¶
This section is empty.
Types ¶
type Check ¶
type Check struct { Tuple Tuple Userdata Userdata Ruleset []InferredRule }
A request to check a given Tuple with the inferred ruleset and the prepared Userdata.
type InferredRule ¶
type InferredRule struct { Kind Kind Object string Subject string Relations []string WithRelationToSubject []string }
An InferredRule is the result of [Rule]s being prepared when a model is instiantiated via NewModel. It is a flattened and preprocessed form of rules that is directly used to interact with Storage-implementations. It merges relations, splits them if multiple [Kind]s apply and it is important to note, that they are also sorted by [InferredRule.Kind] in [Model.InferredRules].
type InferredRuleMap ¶
type InferredRuleMap map[string]map[string][]InferredRule
A map of objects to map of relations to sorted rulesets of InferredRule.
type Kind ¶
type Kind int
[InferredRule]s are precomputed for a given Model based on the ObjectMap and specified [Rule]s. Several types of rules exist, which require different traversals of the authorization model.
const ( // Should never be used, but is used as a default value to make sure [Kind] is always specified. KindUnknown Kind = iota // A direct relationship between object and subject exists, user has direct access to object. KindDirect // A direct relationship between object and usersets exists, e.g. user is part of a group with access to the desired resource. KindDirectUserset // An indirect relationship between object and subject exists through another nested object, e.g. user has access to a folder containing a document. KindIndirect )
type MarkedTuple ¶
A tuple with additional information which Check and which InferredRule from the Check resulted in this tuple. This is used by the Resolver to connect resulting tuples to the original instructions to deduct subsequent actions.
type Model ¶
type Model struct { InferredRules InferredRuleMap // contains filtered or unexported fields }
A Model is the authorization model created from an ObjectMap. During creation the model-definition provided by an ObjectMap is computed into a lower-lever ruleset of [InferredRule]s.
func NewModel ¶
NewModel checks the ObjectMap for correctness and will infer the rules and prepare them for check-resolution.
func (*Model) RulesetFor ¶
func (m *Model) RulesetFor(object, relation string) []InferredRule
Rules are sorted direct first, indirect last. Returns the Rulset for a particular object-type and relation. If the object-type or relation does not exist, nil will be returned.
type ObjectMap ¶
type ObjectMap map[string]RelationMap
The ObjectMap is the primary input of a model and is required to create a model and compute the inferred rules. The key is expected to be the object-type.
The structure is inspired by warrant.
type Pagination ¶
type Resolver ¶
type Resolver struct {
// contains filtered or unexported fields
}
A Resolver uses a Model and Storage-implementation to execute relationship checks.
func NewResolver ¶
NewResolver creates a new resolver for the particular Model using the designated Storage-implementation. The main purpose of a Resolver is to traverse the ReBAC-policies and check whether a Tuple is authorized or not. During creation the inferred rules of the Model are used to precompute storage-specific Userdata that can be used to speed up checks (when calling Storage.QueryChecks internally). When Check is called the Userdata is passed on to the Storage-implementation as part of the Check.
maxDepth limits the depth of the traversal of the authorization-model during checks.
func (*Resolver) RulesetFor ¶
func (r *Resolver) RulesetFor(object, relation string) []InferredRule
Returns an inferred ruleset for the given object-type and relation.
type Rule ¶
type Rule struct { // If InheritIf is set the relation is inheritable from the specified relation, // e.g. `viewer` relationship inherited if subject is `editor`. InheritIf string `json:"inheritIf"` // If OfType is set, the relation specified by InheritIf needs to exist between the subject and the specified object-type. // This requires WithRelation to be set as there needs to be a WithRelation between object and an instance of OfType. OfType string `json:"ofType,omitempty"` // WithRelation defines, which relation needs to exist between OfType and the object to inherit the relationship status. WithRelation string `json:"withRelation,omitempty"` // Rules should not be set directly, but are public to make serializing rules easier. // The purpose of Rules is to allow combining rules. This should be done with functions such as [AnyOf] to properly mark the rule. Rules []Rule `json:"rules,omitempty"` }
A Rule is associated with a relationship of an authorization Model and defines when the requirement of the relationship is met. Without any fields specified the Rule will still be met by direct relations between an object and a subject.
type Storage ¶
type Storage interface { // Creates the [Tuple] t or errors, if creations fails. Write(ctx context.Context, t Tuple) error // Reads the specified [Tuple]. As all fields need to be known to read it, the UUID is returned. // If the tuple was not found, [ErrNotFound] is returned. Read(ctx context.Context, t Tuple) (uuid.UUID, error) CursorStart() Cursor List(ctx context.Context, f Tuple, p Pagination) ([]Tuple, Cursor, error) // PrepareRuleset takes an object-type and relation with the inferred ruleset and prepares // the storage-implementation for subsequent checks by optionally returning [Userdata]. PrepareRuleset(object, relation string, ruleset []InferredRule) (Userdata, error) // QueryChecks will retrieve matching tuples for all the checks if they exist. // The tuples are marked with the CheckIndex and RuleIndex to be able to identify precisely, the associated ruleset. // Returned marked tuples are sorted by RuleIndex as rulesets always begin with direct-relationships. // This allows returning as soon as possible by minimizing the rules to be checked for matches. QueryChecks(ctx context.Context, checks []Check) ([]MarkedTuple, error) Close() error }
Storage provides simple CRUD operations for persistence as well as more complex methods required to permission checks as performant as possible.
type Tuple ¶
type Tuple struct { /// ⟨object⟩ ::= ⟨namespace⟩‘:’⟨object id⟩ ObjectType string `json:"object_type"` ObjectID string `json:"object_id"` /// ⟨relation⟩ ObjectRelation string `json:"relation"` /// ⟨user⟩ ::= ⟨namespace⟩‘:’⟨user id⟩ | ⟨userset⟩ SubjectType string `json:"user_type"` SubjectID string `json:"user_id"` /// ⟨userset⟩ ::= ⟨object⟩‘#’⟨relation⟩ SubjectRelation string `json:"user_relation"` }
⟨tuple⟩ ::= ⟨object⟩‘#’⟨relation⟩‘@’⟨user⟩
func TupleString ¶
Parses a string in Zanzibar-format and returns the resulting tuple. If the string is malformed, EmptyTuple will be returned.
Examples for input are: 'doc:mydoc#viewer@user:myuser' or 'doc:mydoc#editor@group:mygroup#member'
type Userdata ¶
type Userdata any
Marker interface for Userdata returned by a storage-implementation. Primary purpose is to prepare for a given inferred ruleset with Storage.PrepareRuleset and supply the Userdata to subsequent Storage.QueryChecks involving the associated ruleset.
type UserdataMap ¶
A map of object-types to relations to Userdata.