spoc

package module
v0.0.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 28, 2022 License: BSD-3-Clause Imports: 7 Imported by: 0

README

SPOc

Test PkgGoDev Go Report Card

Simple RBAC + data built on top of ArangoDB

Introduction

RBAC

SPOc uses directed acyclic graphs (DAGs) to represent:

  • Subjects (i.e. users and groups),
  • Predicates (i.e. permissions / actions / rights as well as groupings of them), and
  • Objects (i.e. resources and tags).

Having hierarchies of Subjects, Predicates, and Objects, SPOc uses Predicate-annotated edges between Subjects and Objects to represent Rules (i.e. policies).

overview

As of now, SPOc supports three types queries:

  • allowed?: is a given user allowed to perform a given action on a given resource.
  • what?: what resources a given user may perform a given action on.
  • who?: who may perform a given action on a given resource.

Thanks to ArangoDB, those queries are simple and fast (!) graph traversals (on the connected DAGs).

Note, "what?" is essentially "SP?" and "who?" is "?PO". It would be easy to implement things like "S??", "?P?" ...

RBAC + Data

SPOc may be used purely on IDs. However, since ArangoDB is multi-model database system, SPOc readily supports to store the data itself - reliving the need to store the actual data somewhere else.

From RBAC to ABAC ?

Not yet done, but having RBAC + Data it would be possible to implement Conditions / Constraints on Rules. For that one would need to annotate Rules with Constraints (i.e. the "c" in SPOc). However ArangoDB does not support dynamic eval expressions while traversing graphs. Thus, this would need to be done through splitting queries and / or post-processing of results.

Quickstart

Example

Compiling and running:

package main

import (
	"context"
	"fmt"
	"github.com/arangodb/go-driver"
	"github.com/arangodb/go-driver/http"
	"github.com/sebogh/spoc"
	"strconv"
	"time"
)

func main() {
	ctx := context.Background()

	// new ArangoDB-connection
	conn, _ := http.NewConnection(http.ConnectionConfig{Endpoints: []string{"http://localhost:8529"}})

	// new ArangoDB client
	client, _ := driver.NewClient(driver.ClientConfig{Connection: conn})

	// generate UUID for a random db- and SPOC name
	uid := strconv.FormatInt(time.Now().UnixNano(), 10)
	s, _ := spoc.NewSPOC(ctx, "test-"+uid, uid, client)

	// Sebastian belongs to Admin
	_ = s.Subjects.AddRelation(ctx, "Sebastian", "Admin", true)

	// owns includes read
	_ = s.Predicates.AddRelation(ctx, "read", "owns", true)

	// File1 is a Report
	_ = s.Objects.AddRelation(ctx, "File1", "Report", true)

	// Admin may read Reports
	_, _ = s.AddRule(ctx, "Admin", "owns", "Report", false)

	// Test whether Sebastian is allowed to read File1
	allowed, _ := s.Allowed(ctx, "Sebastian", "read", "File1")
	fmt.Println(allowed)
}

will result in:

true

(see: example_basic_test.go)

Explanation

In the above example we added relations between:

  • Sebastian and Admin,
  • read and owns, and
  • File1 and Report.

and implicitly the respective Subjects, Predicates, and Objects.

Then we added the Rule that the Admin-group owns (own) all Resources tagged as Report.

Finally, we ask whether Sebastian is allowed to read File1.

Since Sebastian is an Admin, File1 is a Report and read is included in owns, the SPOc readily answers true.

REST

As a proof of concept SPOc contains an implementation of a REST interface. See cmd/rest.

To try it out, run:

docker-compose up

(which starts a ArangoDB as well as a SPOC-server) and then see http://localhost:8080/swagger.

Following this, you may run:

curl -X 'POST' 'http://localhost:8080/relation/subject/Sebastian/Admin'
curl -X 'POST' 'http://localhost:8080/relation/predicate/read/owns'
curl -X 'POST' 'http://localhost:8080/relation/object/File1/Report'
curl -X 'POST' 'http://localhost:8080/rule/Admin/owns/Report'

to add the same things and rule(s) as in the Quickstart above.

Having this in place, you may try:

curl -X 'GET' 'http://localhost:8080/allowed/Sebastian/read/File1' 

to see whether Sebastian is allowed to read File1. Or:

curl -X 'GET' 'http://localhost:8080/explain/Sebastian/read/File1' | jq 

to get an explanation for the same.

Running:

curl -X 'GET' 'http://localhost:8080/which/Sebastian/read'

will return the list of all resources Sebastian is allowed to read (i.e. File1 and Report).

Now, just for the fun, add another user (Marie) with some data attached and being part of the Admin-group:

curl -X 'POST' 'http://localhost:8080/subject/Marie' -H 'Content-Type: application/json' -d '{"birthday": "06"}'
curl -X 'POST' 'http://localhost:8080/relation/subject/Marie/Admin'
curl -X 'GET' 'http://localhost:8080/subject/Marie'

And finally retrieve all users allowed to read File1:

curl -X 'GET' 'http://localhost:8080/who/read/File1'

Documentation

Overview

Example
ctx := context.Background()

// new ArangoDB-connection
conn, _ := http.NewConnection(http.ConnectionConfig{Endpoints: []string{"http://localhost:8529"}})

// new ArangoDB client
client, _ := driver.NewClient(driver.ClientConfig{Connection: conn})

// generate UUID for a random db- and SPOC name
uid := strconv.FormatInt(time.Now().UnixNano(), 10)
s, _ := spoc.NewSPOC(ctx, "test-"+uid, uid, client)

// Sebastian belongs to Admin
_ = s.Subjects.AddRelation(ctx, "Sebastian", "Admin", true)

// read belongs to owns
_ = s.Predicates.AddRelation(ctx, "read", "owns", true)

// File1 belongs to Root
_ = s.Objects.AddRelation(ctx, "File1", "Root", true)

// Admin may read Root
_, _ = s.AddRule(ctx, "Admin", "owns", "Root", false)

// Test whether Sebastian is allowed to read File1
allowed, _ := s.Allowed(ctx, "Sebastian", "read", "File1")
fmt.Println(allowed)
Output:

true

Index

Examples

Constants

View Source
const (
	SPOCErrNotFound = 2001
)

Variables

View Source
var Cause = func(err error) error { return err }

Functions

func IsSPOCError

func IsSPOCError(err error) bool

IsSPOCError returns true when the given error is an SPOCError.

func IsSPOCErrorWithCode

func IsSPOCErrorWithCode(err error, status int) bool

func IsSPOCErrorWithNumber

func IsSPOCErrorWithNumber(err error, number int) bool

Types

type AllowMatch

type AllowMatch struct {
	Length     int      `json:"len"`
	Subject    string   `json:"subject"`
	Predicate  string   `json:"predicate"`
	Object     string   `json:"object"`
	Subjects   []string `json:"subjects"`
	Predicates []string `json:"predicates"`
	Objects    []string `json:"objects"`
}

type RelationMatch

type RelationMatch struct {
	Child  string `json:"child"`
	Parent string `json:"parent"`
}

type RuleMatch

type RuleMatch struct {
	Subject   string `json:"subject"`
	Predicate string `json:"predicate"`
	Object    string `json:"object"`
}

type SPOC

type SPOC struct {
	Subjects   *Thing
	Predicates *Thing
	Objects    *Thing
	Rules      driver.Collection
	// contains filtered or unexported fields
}

func NewSPOC

func NewSPOC(ctx context.Context, dbName, spocName string, client driver.Client) (s *SPOC, err error)

func (*SPOC) AddRule

func (s *SPOC) AddRule(ctx context.Context, subjectKey, predicateKey, objectKey string, createVertices bool) (meta driver.DocumentMeta, err error)

func (*SPOC) Allowed

func (s *SPOC) Allowed(ctx context.Context, subjectKey, predicateKey, objectKey string) (allowed bool, err error)

func (*SPOC) DelRule

func (s *SPOC) DelRule(ctx context.Context, subjectKey, predicateKey, objectKey string) (err error)

func (*SPOC) Explain

func (s *SPOC) Explain(ctx context.Context, subjectKey, predicateKey, objectKey string, offset, count uint) (matches []AllowMatch, err error)

func (*SPOC) GetRule

func (s *SPOC) GetRule(ctx context.Context, subjectKey, predicateKey, objectKey string) (meta driver.DocumentMeta, err error)

func (*SPOC) GetRules

func (s *SPOC) GetRules(ctx context.Context, offset, count uint) (matches []RuleMatch, err error)

func (*SPOC) SetQueryLogging

func (s *SPOC) SetQueryLogging(queryLogging bool)

SetQueryLogging enables or disables query logging.

func (*SPOC) Which

func (s *SPOC) Which(ctx context.Context, subjectKey, predicateKey string, offset, count uint) (matches []string, err error)

func (*SPOC) Who

func (s *SPOC) Who(ctx context.Context, predicateKey, objectKey string, offset, count uint) (matches []string, err error)

type SPOCError

type SPOCError struct {
	Code    int
	Number  int
	Message string
}

func (SPOCError) Error

func (e SPOCError) Error() string

Error returns the error Message of an ArangoError.

type Thing

type Thing struct {
	*arangodag.DAG
}

Thing embeds a DAG for SPOC-extensions.

func (*Thing) Add

func (t *Thing) Add(ctx context.Context, key string, data interface{}) (meta driver.DocumentMeta, err error)

Add adds a new thing.

func (*Thing) AddRelation

func (t *Thing) AddRelation(ctx context.Context, childKey, parentKey string, createVertices bool) (err error)

AddRelation adds an "is a" relation between two things.

func (*Thing) Del

func (t *Thing) Del(ctx context.Context, key string) (err error)

Del deletes a thing.

func (*Thing) DelRelation

func (t *Thing) DelRelation(ctx context.Context, childKey, parentKey string) (err error)

DelRelation deletes an "is a" relation between two things.

func (*Thing) Get

func (t *Thing) Get(ctx context.Context, key string, data interface{}) (meta driver.DocumentMeta, err error)

Get returns a thing.

func (*Thing) IsRelation

func (t *Thing) IsRelation(ctx context.Context, childKey, parentKey string) (exists bool, err error)

IsRelation returns true, if an "is a" relation between two things exists.

func (*Thing) List

func (t *Thing) List(ctx context.Context, offset, count uint) (keys []string, err error)

List returns an ordered list of (max) count keys of things - starting with offset.

func (*Thing) ListRelations

func (t *Thing) ListRelations(ctx context.Context, offset, count uint) (matches []RelationMatch, err error)

ListRelations returns an ordered list of (max) count "is a" relations - starting with offset.

func (*Thing) Replace

func (t *Thing) Replace(ctx context.Context, key string, data interface{}) (meta driver.DocumentMeta, err error)

Replace replaces the data of a thing.

func (*Thing) Update

func (t *Thing) Update(ctx context.Context, key string, data interface{}) (meta driver.DocumentMeta, err error)

Update updates the data of a thing.

type WhichMatch

type WhichMatch struct {
	Object string       `json:"object"`
	Rules  []AllowMatch `json:"rules"`
}

Directories

Path Synopsis
cmd

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL