authr

package module
v2.0.2+incompatible Latest Latest
Warning

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

Go to latest
Published: Jul 9, 2019 License: BSD-3-Clause Imports: 8 Imported by: 0

README

authr Build Status

a flexible, expressive, language-agnostic access-control framework.

how it works

authr is an access-control framework. describing it as a "framework" is intentional because out of the box it is not going to automatically start securing your application. it is extremely agnostic about quite a few things. it represents building blocks that can be orchestrated and put together in order to underpin an access-control system. by being so fundamental, it can fit almost any need when it comes to controlling access to specific resources in a particular system.

vocabulary

the framework itself has similar vocabulary to an ABAC access-control system. the key terms are explained below.

subject

a subject in this framework represents an entity that is capable of performing actions; an actor if you will. in most cases this will represent a "user" or an "admin".

resource

a resource represents an entity which can be acted upon. in a blogging application this might be a "post" or a "comment". those are things which can be acted upon by subjects wanting to "edit" them or "delete" them. it is worth noting that subjects can also be resources — a "user" is something that can act and be acted upon.

a resource has attributes which can be analyzed by authr. for example, a post might have an attribute id which is 333. or, a user might have an attribute email which would be person@awesome.blog.

action

an action is a simple, terse description of what action is being attempted. if say a "user" was attempting to fix a typo in their "post", the action might just be edit.

rule

a rule is a statement that composes conditions on resource and actions and specifies whether to allow or deny the attempt if the rule is matched. so, for example if you wanted to "allow" a subject to edit a private post, the JSON representation of the rule might look like this:

{
    "access": "allow",
    "where": {
        "action": "edit",
        "rsrc_type": "post",
        "rsrc_match": [
            ["@type", "=", "private"]
        ]
    }
}

notice the lack of anything that specifies conditions on who is actually performing the action. this is important; more on that in a second.

agnosticism through interfaces

across implementations, authr requires that objects implement certain functionality so that its engine can properly analyze resources against a list of rules that belong to a subject.

once the essential objects in an application have implemented these interfaces, the essential question can finally be asked: can this subject perform this action on this resource?

<?php

use Cloudflare\Authr;

class UserController extends Controller
{
    /** @var \Cloudflare\AuthrInterface */
    private $authr;
    ...

    public function update(Request $req, Response $res, array $args)
    {
        // get the subject
        $subject = $req->getActor();

        // get the resource
        $resource = $this->getUser($args['id']);

        // check permissions!
        if (!$this->authr->can($subject, 'update', $resource)) {
            throw new HTTPException\Forbidden('Permission denied!');
        }

        ...
    }
}
forming the subject

authr is most of the time identifiable as an ABAC framework. it relies on the ability to place certain conditions on the attributes of resources. there is however one key difference: there is no way to specify conditions on the subject in rule statements.

instead, the only way to specify that a specific actor is able to perform an action on a resource is to emit a rule from the returned list of rules that will match the action and allow it to happen. therefore, a subject is only ever known as a list of rules.

type Subject interface {
    GetRules() ([]*Rule, error)
}

and instead of the rules being statically defined somewhere and needing to make the framework worry about where to retrieve the rules from, rules belong to subjects and are only ever retrieved from the subject.

when permissions are checked, the framework will simply call a method available via an interface on the subject to retrieve a list of rules for that specific subject. then, it will iterate through that list until it matches a rule and return a boolean based on whether the rule wanted to allow or deny.

why disallow inspection of attributes on the actor?

by reducing actors to just a list of rules, it condenses all of the logic about what a subject is capable of to a single area and keeps it from being scattered all over an application's codebase.

also, in traditional RBAC access-control systems, the notion of checking if a particular actor is in a certain "role" or checking the actors ID to determine access is incredibly brittle and "ages" a codebase.

by having a single component which is responsible for answering the question of access-control, combined with being forced to clearly express what an actor can do with the authr rules, it leads to an incredible separation of concerns and a much more sustainable codebase.

even if authr is not the access-control you choose, there is a distinct advantage to organizing access-control in your services this way, and authr makes sure that things stay that way.

expressing permissions across service boundaries

because the basic unit of permission in authr is a rule defined in JSON, it is possible to let other services do the access-control checks for their own purposes.

an example of this internally at Cloudflare is in a administrative service. by having this permissions defined in JSON, we can simply transfer all the rules down to the front-end (in JavaScript) and allow the front-end to hide/show certain functionality based on the permission of whoever is logged in.

when you can have the front-end and the back-end of a service seamlessly agreeing with each other on access-control by only updating a single rule, once, it can lead to much easier maintainability.

todo

  • create integration tests that ensure implementations agree with each other
  • finish go implementation
  • add examples of full apps using authr for access-contro
  • add documentation about the rules format

Documentation

Overview

Package authr is an application-level (layer 7) access control framework.

Index

Constants

View Source
const (

	// ImpliedConjunction is the default conjunction on condition sets that do
	// not have an explicit conjunction
	ImpliedConjunction = logicalAnd
)
View Source
const Version = "2.0.1"

Variables

This section is empty.

Functions

func Can

func Can(s Subject, action string, r Resource) (bool, error)

Can is the core access control computation function. It takes in a subject, action, and resource. It will answer the question "Can this subject perform this action on this resource?".

Types

type Access

type Access string

Access represents a value which will distinguish a rule as either being a restricting rule or a permitting one.

const (
	// Allow when set as the "access" on a rule will return true when the rule
	// is matched
	Allow Access = "allow"

	// Deny when set as the "access" on a rule will return false when the rule
	// is matched
	Deny Access = "deny"
)

type ConditionSet

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

func ResourceMatch

func ResourceMatch(es ...Evaluator) ConditionSet

ResourceMatch is just a more readable way to start the rsrc_match section of a rule. It uses the implied logical conjunction AND.

type Error

type Error string

Error is used for any error that occurs during authr's evaluation. They are normally returned as a result of improperly constructed rules.

func (Error) Error

func (e Error) Error() string

type Evaluator

type Evaluator interface {
	// contains filtered or unexported methods
}

Evaluator is an abstract representation of something that is capable of analyzing a Resource

func And

func And(subEvaluators ...Evaluator) Evaluator

And returns an Evaluator that combines multiple Evaluators and will evaluate the set of evaluators with the logical conjunction AND. The behavior of the AND evaluator is to evaluate each sub-evaluator in order until one returns false or all return true. Once it finds a negative evaluator, it will halt and return — also known as short-circuiting.

func Cond

func Cond(left interface{}, op string, right interface{}) Evaluator

Cond is the basic unit of a resource match section of a rule. It represents a single condition to be evaluated against a Resource. Constructing a condition should be quite natural, like so:

Cond("@id", "=", "123")

The above condition says that the "id" attribute on a resource MUST equal 123. References to resource attributes are prefixed with an "@" character to distinguish them from literal values. To specify multiple conditions, use the condition sets:

And(
    Cond("@status", "=", "active"),
    Cond("@name", "$in", []string{
        "mike",
        "jane",
        "rachel",
    }),
)

func Or

func Or(subEvaluators ...Evaluator) Evaluator

Or returns an Evaluator that is just like And, except it evaluate with the OR logical conjunction. Meaning it will evaluate until a sub-evaluator returns true, and also short-circuit.

type Resource

type Resource interface {
	GetResourceType() (string, error)
	GetResourceAttribute(string) (interface{}, error)
}

Resource is an abstract representation of an entity the is the target of actions performed by subjects. Resources have a type and attributes.

A "type" is what you might expect. If a blog were in need of an access control system, the resource type for a post would simply be "post" and the writers "author" perhaps.

Attributes are any properties of a resource that can be evaluated. A post, for example, can have "tags", which when being retrieve with GetResourceAttribute() would return a slice of strings.

Unknown or missing properties should simply return "nil" and not an error.

type Rule

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

Rule represents the basic building block of an access control system. They can be likened to a single statement in an access-control list (ACL). Rules are entities which are said to "belong" to subjects in that they have been granted or applied to subjects based on the state of a datastore or the state of the subject themselves.

Building rules in Go (instead of say Unmarshaling from JSON) looks like this:

r := new(Rule).
    Access(Allow).
    Where(
        Action("delete"),
        Not(ResourceType("user")),
        ResourceMatch(
            Cond("@id", "!=", "1"),
            Or(
                Cond("@status", "=", "active"),
                Cond("@deleted_date", "=", nil),
            ),
        ),
    )

This can be quite verbose, externally. A suggestion to reduce the verbosity might be to have a dedicate .go file that specifies rules where you can dot import authr. (https://golang.org/ref/spec#Import_declarations)

func (Rule) Access

func (r Rule) Access(at Access) *Rule

func (Rule) Meta

func (r Rule) Meta(meta interface{}) *Rule

func (*Rule) UnmarshalJSON

func (r *Rule) UnmarshalJSON(data []byte) error

func (Rule) Where

func (r Rule) Where(action, resourceType SlugSet, conditions ConditionSet) *Rule

type SlugSet

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

SlugSet is an internal means of representing an arbitrary set of strings. The "rsrc_type" and "action" sections of a rule have this type.

func Action

func Action(sset ...string) SlugSet

Action allows for the specification of actions in a rule. The default mode is an "allowlist". Use Not(Action(...)) to specify a "blocklist"

func Not

func Not(s SlugSet) SlugSet

Not will return a copy of the provided SlugSet that will operate in a blocklist mode. Meaning the elements if matched in a calculation will return "false"

func ResourceType

func ResourceType(sset ...string) SlugSet

ResourceType allows for the specification of resource types in a rule. The default mode is an "allowlist". Use Not(Action(...)) to specify a "blocklist"

func (SlugSet) Not

func (s SlugSet) Not() SlugSet

Not is a way to turn a SlugSet into a blocklist instead of the default allowlist mode.

DEPRECATED: this API is awkward, use the authr.Not(Action("foo", "bar")) method instead. TODO(nkcmr): remove this in v3 of authr

type Subject

type Subject interface {
	// GetRules simply retrieves a list of rules. The ordering of these rules
	// does matter. The rules themselves can be retrieve by any means necessary —
	// whether it be from a database or a config file; whatever works.
	GetRules() ([]*Rule, error)
}

Subject is an abstract representation of an entity capable of performing actions on resources. It is distinguished by have a method which is supposed to return a list of rules that apply to the subject.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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