fastac

package module
v1.1.0 Latest Latest
Warning

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

Go to latest
Published: May 8, 2022 License: Apache-2.0 Imports: 13 Imported by: 0

README

FastAC

access control for go, supports RBAC, ABAC and ACL, drop-in replacement for casbin

Test Coverage Go Report Card Godoc

FastAC is a drop in replacement for Casbin. In some cases, FastAC can improve the performance significantly.

API documentation: https://pkg.go.dev/github.com/abichinger/fastac

Please refer to the Casbin Docs for explanation of terms.

Getting Started

Installation

go get github.com/abichinger/fastac

First you need to prepare an access control model. The syntax of FastAC models is identical to Casbin models.

An ACL (Access Control List) model looks like this:

#File: model.conf

[request_definition]
r = sub, obj, act

[policy_definition]
p = sub, obj, act

[policy_effect]
e = some(where (p.eft == allow))

[matchers]
r.sub == p.sub && r.obj == p.obj && r.act == p.act

Next, you need to load some policy rules. To get started you can load your rules from a text file. For production you should use a storage adapter.

#File: policy.csv
p, alice, data1, read
p, alice, data2, read
p, bob, data1, write
p, bob, data2, write

Go code to resolve access requests

//create an enforcer
e, err := fastac.NewEnforcer("model.conf", "policy.csv")

//check if alice is allowed to read data1
if allow, _ := e.Enforce("alice", "data1", "read"); allow == true {
    // permit alice to read data1
} else {
    // deny the request
}

New Features

Policy Indexing

Matchers will be divided into multiple stages. As a result FastAC will index all policy rules, which reduces the search space for access requests. This feature brings the most performance gain.

Advanced Policy Filtering

FastAC can filter the policy rules with matchers. The Filter function also supports filtering grouping rules. The fields of a grouping rule can be accessed by g.user, g.role, g.domain

//Examples

//get all policy rules belonging to domain1
e.Filter(SetMatcher("p.dom == \"domain1\"")

//get all policy rules, which grant alice read access
e.Filter(SetMatcher("g(\"alice\", p.sub) && p.act == \"read\"")

//get all grouping rules for alice
e.Filter(SetMatcher("g.user == \"alice\"")

Supported Models

  • ACL - Access Control List
  • ACL-su - Access Control List with super user
  • ABAC - Attribute Based Access Control
  • RBAC - Role Based Access Control
  • RBAC-domain - Role Based Access Control with domains/tenants

Adapter List

  • File Adapter (built-in) - not recommended for production
  • Gorm Adapter

Performance Comparison

RBAC Benchmark

ABAC Benchmark

More benchmarks

Feature Overview

  • Enforcement
  • RBAC
  • ABAC
  • Adapter
  • Default Role Manager
  • Third Party Role Managers
  • Filtered Adapter
  • Watcher
  • Dispatcher

Attribution

FastAC uses the following libraries or parts of it.

Documentation

Overview

Example (Functions)

ExampleFunctions shows how to use a custom util.MatchingFunc

package main

import (
	"github.com/abichinger/fastac"
	"github.com/abichinger/fastac/model"
	"github.com/abichinger/fastac/model/fm"
)

// the model uses a custom MatchingFunc named customPathMatch
var example_functions_model = `
[request_definition]
r = sub, obj, act

[policy_definition]
p = sub, obj, act

[policy_effect]
e = some(where (p.eft == allow))

[matchers]
m = r.sub == p.sub && customPathMatch(r.obj, p.obj) && r.act == p.act`

var example_functions_policy = [][]string{
	{"p", "alice", "*", "GET"},
	{"p", "alice", "/user/alice", "PATCH"},
}

// ExampleFunctions shows how to use a custom util.MatchingFunc
func main() {

	//customPathMatch needs to be registered before loading the model
	fm.SetFunction("customPathMatch", func(arguments ...interface{}) (interface{}, error) {
		rObj := arguments[0].(string)
		rSub := arguments[1].(string)

		if rSub == "*" {
			return true, nil
		}
		return rObj == rSub, nil
	})

	//create enforcer and add rules
	m := model.NewModel()
	_ = m.LoadModelFromText(example_functions_model)
	e, _ := fastac.NewEnforcer(m, nil)
	_ = e.AddRules(example_functions_policy)

	//perform some requests
	printReq(e, "alice", "/user/alice/entry/1", "GET")
	printReq(e, "bob", "/user/alice/entry/1", "GET")
	printReq(e, "alice", "/user/alice", "PATCH")
	printReq(e, "bob", "/user/alice", "PATCH")

}
Output:

alice, /user/alice/entry/1, GET => allow
bob, /user/alice/entry/1, GET => deny
alice, /user/alice, PATCH => allow
bob, /user/alice, PATCH => deny
Example (ManagePolicy)

ExampleManagePolicy demonstrates the usage of functions to modify the policy

package main

import (
	"fmt"
	"sort"

	"github.com/abichinger/fastac"
	"github.com/abichinger/fastac/util"
)

var example_rules_policy = [][]string{
	{"p", "alice", "data1", "read"},
	{"p", "alice", "data1", "write"},
	{"p", "bob", "data2", "read"},
	{"p", "bob", "data2", "write"},
	{"p", "alice", "data3", "read"},
	{"p", "bob", "data3", "read"},
	{"p", "manager", "data3", "write"},
	{"g", "bob", "manager"},
}

// ExampleManagePolicy demonstrates the usage of functions to modify the policy
func main() {

	//create enforcer with rbac model and empty policy
	e, _ := fastac.NewEnforcer("examples/rbac_model.conf", nil)

	//add multiple rules at once
	_ = e.AddRules(example_rules_policy)

	//remove all rules of user bob
	bobRules, _ := e.Filter(fastac.SetMatcher(`p.sub == "bob"`))
	bobGroupingRules, _ := e.Filter(fastac.SetMatcher(`g.user == "bob"`))
	_ = e.RemoveRules(append(bobRules, bobGroupingRules...))

	//make alice a manager
	alice_manager := []string{"g", "alice", "manager"}
	added, _ := e.AddRule(alice_manager)
	if added {
		fmt.Println("rule added successfully")
	}

	//get a list of all rules
	var allRules [][]string
	e.GetModel().RangeRules(func(rule []string) bool {
		allRules = append(allRules, rule)
		return true
	})

	//sort and print rules
	allRulesStr := util.Join2D(allRules, ", ")
	sort.Strings(allRulesStr)
	for _, rule := range allRulesStr {
		fmt.Println(rule)
	}

}
Output:

rule added successfully
g, alice, manager
p, alice, data1, read
p, alice, data1, write
p, alice, data3, read
p, manager, data3, write
Example (Matchers)

ExampleMatchers shows the usage of util.MatchingFunc and util.IMatcher

package main

import (
	"fmt"
	"strings"

	"github.com/abichinger/fastac"
	"github.com/abichinger/fastac/model"
	"github.com/abichinger/fastac/rbac"
	"github.com/abichinger/fastac/util"
)

// the model uses the built-in MatchingFunc pathMatch
var example_matcher_model = `
[request_definition]
r = sub, obj, act

[policy_definition]
p = sub, obj, act

[role_definition]
g = _, _

[policy_effect]
e = some(where (p.eft == allow))

[matchers]
m = g(r.sub, p.sub) && pathMatch(r.obj, p.obj) && r.act == p.act`

var example_matcher_policy = [][]string{
	{"p", "role:user", "/user/:uid/entry/:eid", "GET"},
	{"p", "user:alice", "/user/alice/*", "POST"},
	{"p", "role:admin", "/user/:uid/entry/:eid", "DELETE"},
	{"g", "reg:user:.*", "role:user"},
	{"g", "user:alice", "role:admin"},
}

func printReq(e *fastac.Enforcer, params ...interface{}) {
	b, _ := e.Enforce(params...)
	var rule []string
	for _, param := range params {
		rule = append(rule, param.(string))
	}
	if b {
		fmt.Printf("%s => allow\n", strings.Join(rule, ", "))
	} else {
		fmt.Printf("%s => deny\n", strings.Join(rule, ", "))
	}
}

// ExampleMatchers shows the usage of util.MatchingFunc and util.IMatcher
func main() {

	//create enforcer and add rules
	m := model.NewModel()
	_ = m.LoadModelFromText(example_matcher_model)
	e, _ := fastac.NewEnforcer(m, nil)
	_ = e.AddRules(example_matcher_policy)

	//get the default rolemanager
	rm, _ := e.GetModel().GetRoleManager("g")

	// set a role matcher.
	// create a PrefixMatcher. PrefixMatcher implements the interface util.IMatcher
	// each regex pattern needs to be marked with the prefix "reg:"
	roleMatcher := util.NewPrefixMatcher("reg:", util.RegexMatch)
	rm.(rbac.IDefaultRoleManager).SetMatcher(roleMatcher)

	printReq(e, "user:alice", "/user/joe/entry/1", "GET") //allow, because user:alice has role:user
	printReq(e, "user:alice", "/user/alice/entry/2", "POST")
	printReq(e, "user:alice", "/user/bob/entry/3", "POST")
	printReq(e, "user:alice", "/user/bob/entry/3", "DELETE")
	printReq(e, "user:bob", "/user/alice/entry/2", "DELETE")

}
Output:

user:alice, /user/joe/entry/1, GET => allow
user:alice, /user/alice/entry/2, POST => allow
user:alice, /user/bob/entry/3, POST => deny
user:alice, /user/bob/entry/3, DELETE => allow
user:bob, /user/alice/entry/2, DELETE => deny
Example (StorageAdapter)

ExampleStorageAdapter shows how to store/load policy rules to/from a storage adapter

package main

import (
	"fmt"
	"os"
	"sort"
	"strings"

	"github.com/abichinger/fastac"
	gormadapter "github.com/abichinger/gorm-adapter"
	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
)

var example_rules_1 = [][]string{
	{"p", "alice", "data1", "read"},
	{"p", "alice", "data1", "write"},
	{"p", "bob", "data1", "read"},
}

func createDB(name string) *gorm.DB {
	_ = os.Mkdir(".tmp", 0755)
	db, _ := gorm.Open(sqlite.Open(".tmp/"+name+".db"), &gorm.Config{})
	return db
}

func removeDB(name string) {
	os.Remove(".tmp/" + name + ".db")
}

// ExampleStorageAdapter shows how to store/load policy rules to/from a storage adapter
func main() {

	//init adapter
	db := createDB("example")
	defer removeDB("example")
	a, err := gormadapter.NewAdapter(db)
	if err != nil {
		panic(err)
	}

	//create enforcer and store rules using the autosave feature
	e, _ := fastac.NewEnforcer("examples/basic_model.conf", a, fastac.OptionAutosave(true))
	err = e.AddRules(example_rules_1)
	if err != nil {
		panic(err)
	}

	//second enforcer to demonstrate LoadPolicy
	e2, _ := fastac.NewEnforcer("examples/basic_model.conf", a)
	err = e2.LoadPolicy()
	if err != nil {
		panic(err)
	}

	loadedRules := []string{}
	e2.GetModel().RangeRules(func(rule []string) bool {
		loadedRules = append(loadedRules, strings.Join(rule, ", "))
		return true
	})

	sort.Strings(loadedRules)
	for _, rule := range loadedRules {
		fmt.Println(rule)
	}
}
Output:

p, alice, data1, read
p, alice, data1, write
p, bob, data1, read

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Context

type Context struct {
	// contains filtered or unexported fields
}

func NewContext

func NewContext(model model.IModel, options ...ContextOption) (*Context, error)

type ContextOption

type ContextOption func(ctx *Context) error

func SetEffector

func SetEffector(effector interface{}) ContextOption

func SetMatcher

func SetMatcher(matcher interface{}) ContextOption

func SetRequestDef

func SetRequestDef(definition interface{}) ContextOption

type Enforcer

type Enforcer struct {
	// contains filtered or unexported fields
}

func NewEnforcer

func NewEnforcer(model interface{}, adapter interface{}, options ...Option) (*Enforcer, error)

NewEnforcer creates a new Enforcer instance. An Enforcer is the main item of FastAC

Without adapter and default options:

NewEnforcer("model.conf", nil)

With adapter and autosave enabled

adapter := gormadapter.NewAdapter(db, tableName)
NewEnforcer("model.conf", adapter, OptionAutosave(true))

func (*Enforcer) AddRule

func (e *Enforcer) AddRule(rule []string) (bool, error)

AddRule adds a rule to the model Returns false, if the rule was already present

Add policy rule:

e.AddRule([]string{"p", "alice", "data1", "read"})

Add grouping rule:

e.AddRule([]string{"g", "alice", "group1"})

func (*Enforcer) AddRules

func (e *Enforcer) AddRules(rules [][]string) error

AddRules adds multiple rules to the model

func (*Enforcer) Enforce

func (e *Enforcer) Enforce(params ...interface{}) (bool, error)

Enforce decides whether to allow or deny a request It is possible to pass ContextOptions, everything else will be treated as a request value

func (*Enforcer) EnforceWithContext

func (e *Enforcer) EnforceWithContext(ctx *Context, rvals ...interface{}) (bool, error)

func (*Enforcer) Filter

func (e *Enforcer) Filter(params ...interface{}) ([][]string, error)

Filter will fetch all rules which match the given request It is possible to pass ContextOptions, everything else will be treated as a request value The effect of rules is not considered.

Get all permissons from alice:

e.Filter(SetMatcher("p.user == \"alice\""))

Get all grouping rules in domain1:

e.Filter(SetMatcher("g.domain == \"domain1\""))

func (*Enforcer) FilterWithContext

func (e *Enforcer) FilterWithContext(ctx *Context, rvals ...interface{}) ([][]string, error)

func (*Enforcer) Flush

func (e *Enforcer) Flush() error

Flush sends all the modifications of the rule set to the storage adapter.

store rule, when autosave is disabled:

e.AddRule("g", "alice", "group1")
e.Flush()

func (*Enforcer) GetAdapter added in v1.1.0

func (e *Enforcer) GetAdapter() storage.Adapter

func (*Enforcer) GetModel

func (e *Enforcer) GetModel() m.IModel

func (*Enforcer) GetStorageController

func (e *Enforcer) GetStorageController() *storage.StorageController

func (*Enforcer) LoadPolicy

func (e *Enforcer) LoadPolicy() error

LoadPolicy loads all rules from the storage adapter into the model. The model is not cleared before the loading process

func (*Enforcer) RangeMatches

func (e *Enforcer) RangeMatches(params []interface{}, fn func(rule []string) bool) error

func (*Enforcer) RangeMatchesWithContext

func (e *Enforcer) RangeMatchesWithContext(ctx *Context, rvals []interface{}, fn func(rule []string) bool) error

func (*Enforcer) RemoveRule

func (e *Enforcer) RemoveRule(rule []string) (bool, error)

RemoveRule removes a rule from the model Returns false, if the rule was not present

Add policy rule:

e.RemoveRule([]string{"p", "alice", "data1", "read"})

Add grouping rule:

e.RemoveRule([]string{"g", "alice", "group1"})

func (*Enforcer) RemoveRules

func (e *Enforcer) RemoveRules(rules [][]string) error

RemoveRules removes multiple rules from the model

func (*Enforcer) SavePolicy

func (e *Enforcer) SavePolicy() error

SavePolicy stores all rules from the model into the storage adapter.

func (*Enforcer) SetAdapter

func (e *Enforcer) SetAdapter(adapter storage.Adapter)

SetAdapter sets the storage adapter

func (*Enforcer) SetModel added in v1.1.0

func (e *Enforcer) SetModel(model m.IModel)

func (*Enforcer) SetOption

func (e *Enforcer) SetOption(option Option) error

SetOption applies an option to the Enforcer

type IEnforcer

type IEnforcer interface {
	SetOption(option Option) error
	GetStorageController() *storage.StorageController

	GetModel() model.IModel
	SetModel(m model.IModel)

	GetAdapter() storage.Adapter
	SetAdapter(storage.Adapter)

	AddRule(rule []string) (bool, error)
	AddRules(rules [][]string) error
	RemoveRule(rule []string) (bool, error)
	RemoveRules(rules [][]string) error

	LoadPolicy() error
	SavePolicy() error

	Enforce(params ...interface{}) (bool, error)
	EnforceWithContext(ctx *Context, rvals ...interface{}) (bool, error)

	Filter(params ...interface{}) ([][]string, error)
	FilterWithContext(ctx *Context, rvals ...interface{}) ([][]string, error)

	RangeMatches(params []interface{}, fn func(rule []string) bool) error
	RangeMatchesWithContext(ctx *Context, rvals []interface{}, fn func(rule []string) bool) error

	Flush() error
}

type Option

type Option func(*Enforcer) error

func OptionAutosave

func OptionAutosave(autosave bool) Option

Option to disable/enable the autosave feature (default: disabled) If autosave is disabled, Flush needs to be called to save modified rules Enable autosave:

NewEnforcer(model, adapter, OptionAutosave(true))

Or:

e.SetOption(OptionAutosave(true))

func OptionStorage

func OptionStorage(enable bool) Option

Option to disable/enable the storage feature (default: enabled, if an adapter is supplied) If storage is disabled, the StorageController will not listen for rule updates

Directories

Path Synopsis
eft
fm

Jump to

Keyboard shortcuts

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