ladon

package module
v0.0.0-...-f910ed2 Latest Latest
Warning

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

Go to latest
Published: May 31, 2016 License: Apache-2.0 Imports: 14 Imported by: 0

README

Ladon

Build Status Coverage Status Go Report Card

Ladon

Ladon is the serpent dragon protecting your resources. A policy based authorization library written in Go. Ships with PostgreSQL and RethinkDB storage interfaces.

Utilizes ory-am/dockertest V2 for tests. Please refer to ory-am/dockertest for more information on how to setup testing environment.

Table of Contents

Ladon utilizes ory-am/dockertest for tests. Please refer to ory-am/dockertest for more information of how to setup testing environment.

Installation

go get github.com/ory-am/ladon

We recommend to use Glide or Godep, because there might be breaking changes in the future.

What is this and how does it work?

Ladon is an access control library. You might also call it a policy administration and policy decision point. Ladon answers the question:

Who is able to do what on something with some context

  • Who An arbitrary unique subject name, for example "ken" or "printer-service.mydomain.com".
  • Able: The effect which is always "allow" or "deny".
  • What: An arbitrary action name, for example "delete", "create" or "scoped:action:something".
  • Something: An arbitrary unique resource name, for example "something", "resources:articles:1234" or some uniform resource name like "urn:isbn:3827370191".
  • Context: The current context which may environment information like the IP Address, request date, the resource owner name, the department ken is working in and anything you like.
Ladon vs ACL

An access control list (ACL), with respect to a computer file system, is a list of permissions attached to an object. An ACL specifies which users or system processes are granted access to objects, as well as what operations are allowed on given objects. Each entry in a typical ACL specifies a subject and an operation. For instance, if a file object has an ACL that contains (Alice: read,write; Bob: read), this would give Alice permission to read and write the file and Bob to only read it. - Source

Compare this with Ladon and you get:

  • Who: The ACL subject (Alice, Bob).
  • What: The Operation or permission.
  • Something: The object.

ACL however is a white list (Alice is granted permission read on object foo). Ladon however can be used to blacklist as well: Alice is disallowed permission read on object foo.

Without tweaking, ACL does not support departments, ip addresses, request dates and other environmental information. Ladon does.

Ladon vs RBAC

In computer systems security, role-based access control (RBAC) is an approach to restricting system access to authorized users. RBAC is sometimes referred to as role-based security. Within an organization, roles are created for various job functions. The permissions to perform certain operations are assigned to specific roles. Members or staff (or other system users) are assigned particular roles, and through those role assignments acquire the computer permissions to perform particular computer-system functions. Since users are not assigned permissions directly, but only acquire them through their role (or roles), management of individual user rights becomes a matter of simply assigning appropriate roles to the user's account; this simplifies common operations, such as adding a user, or changing a user's department. - Source

Compare this with Ladon and you get:

  • Who: The role
  • What: The Operation or permission.

Again, RBAC is a white list. RBAC does not know objects (something) neither does RBAC know contexts.

How could Ladon work in my environment?

Ladon does not come with a HTTP handler. We believe that it is your job to decide if you want to use Protobuf, RESTful, HTTP, AMPQ, or some other protocol. It's up to you to write handlers!

The following examples will give you a better understanding of what you can do with Ladon.

Access request without context

A valid access request and policy requires at least the affected subject, action and effect:

> curl \
      -X POST \
      -H "Content-Type: application/json" \
      -d@- \
      "https://ladon.myorg.com/policies" <<EOF
      {
          "description": "One policy to rule them all.",
          "subjects": ["users:peter", "users:ken", "groups:admins"],
          "actions" : ["delete"],
          "resources": [
            "<.*>"
          ],
          "effect": "allow"
      }
  EOF
> curl \
      -X POST \
      -H "Content-Type: application/json" \
      -d@- \
      "https://ladon.myorg.com/warden" <<EOF
      {
          "subject": "users:peter",
          "action" : "delete"
      }
  EOF

{
    "allowed": true
}

Note: Because resources matches everything (.*), it is not required to pass a resource name to the warden.

Access request with resource and context

The next example uses resources and (context) conditions to further refine access control requests.

> curl \
      -X POST \
      -H "Content-Type: application/json" \
      -d@- \
      "https://ladon.myorg.com/policies" <<EOF
      {
          "description": "One policy to rule them all.",
          "subjects": ["users:peter", "users:ken", "groups:admins"],
          "actions" : ["delete"],
          "effect": "allow",
          "resources": [
            "resource:articles<.*>"
          ],
          "conditions": {
            "remoteIP": {
                "type": "CIDRCondition",
                "options": {
                    "cidr": "192.168.0.1/16"
                }
            }
          }
      }
  EOF
> curl \
      -X POST \
      -H "Content-Type: application/json" \
      -d@- \
      "https://ladon.myorg.com/warden" <<EOF
      {
          "subject": "users:peter",
          "action" : "delete",
          "resource": "resource:articles:ladon-introduction",
          "context": {
            "remoteIP": "192.168.0.5"
          }
      }
  EOF

{
    "allowed": true
}

Usage

Policies

Policies are an essential part of Ladon. Policies are documents which define who is allowed to perform an action on a resource. Policies must implement the ladon.Policy interface. A standard implementation of this interface is ladon.DefaultPolicy:

import "github.com/ory-am/ladon"

var pol = &ladon.DefaultPolicy{
	// A required unique identifier. Used primarily for database retrieval.
	ID: "68819e5a-738b-41ec-b03c-b58a1b19d043",

	// A optional human readable description.
	Description: "something humanly readable",

	// A subject can be an user or a service. It is the "who" in "who is allowed to do what on something".
	// As you can see here, you can use regular expressions inside < >.
	Subjects: []string{"max", "peter", "<zac|ken>"},

	// Which resources this policy affects.
	// Again, you can put regular expressions in inside < >.
	Resources: []string{"myrn:some.domain.com:resource:123", "myrn:some.domain.com:resource:345", "myrn:something:foo:<.+>"},

	// Which actions this policy affects. Supports RegExp
	// Again, you can put regular expressions in inside < >.
	Actions: []string{"<create|delete>", "get"},

	// Should access be allowed or denied?
	// Note: If multiple policies match an access request, ladon.DenyAccess will always override ladon.AllowAccess
	// and thus deny access.
	Effect: ladon.AllowAccess,

	// Under which conditions this policy is "active".
	Conditions: ladon.Conditions{
		// In this example, the policy is only "active" when the requested subject is the owner of the resource as well.
		"resourceOwner": &ladon.EqualsSubjectCondition{},

		// Additionally, the policy will only match if the requests remote ip address matches address range 127.0.0.1/32
		"remoteIPAddress": &ladon.CIDRCondition{
			CIDR: "127.0.0.1/32",
		},
	},
}

Policy management

Ladon comes with ladon.Manager, a policy management interface which is implemented using RethinkDB and PostgreSQL. Storing policies.

A word on Condition creators Unmarshalling lists with multiple types is not trivial in Go. Ladon comes with creators (factories) for the different conditions. The manager receives a list of allowed condition creators who assist him in finding and creating the right condition objects.

In memory
import (
	"github.com/ory-am/ladon"
	"github.com/ory-am/ladon/memory"
)


func main() {
	warden := &ladon.Ladon{
		Manager: memory.New(),
	}
	err := warden.Manager.Create(pol)

    // ...
}
Using a backend

You will notice that all persistent implementations require an additional argument when setting up. This argument is called allowedConditionCreators and must contain a list of allowed condition creators (or "factories"). Because it is not trivial to unmarshal lists of various types (required by ladon.Conditions), we wrote some helpers to do that for you.

You can always pass ladon.DefaultConditionCreators which contains a list of all available condition creators.

PostgreSQL
import "github.com/ory-am/ladon"
import "github.com/ory-am/ladon/postgres"
import "database/sql"
import _ "github.com/lib/pq"

func main() {
    db, err = sql.Open("postgres", "postgres://foo:bar@localhost/ladon")
	if err != nil {
		log.Fatalf("Could not connect to database: %s", err)
	}

    warden := ladon.Ladon{
        Manager: postgres.New(db),
    }

    // ...
}
Warden

Now that we have defined our policies, we can use the warden to check if a request is valid. ladon.Ladon, which is the default implementation for the ladon.Warden interface defines ladon.Ladon.IsAllowed() which will return nil if the access request can be granted and an error otherwise.

import "github.com/ory-am/ladon"

func main() {
    // ...

    if err := warden.IsAllowed(&ladon.Request{
        Subject: "peter",
        Action: "delete",
        Resource: "myrn:some.domain.com:resource:123",
    }); err != nil {
        log.Fatal("Access denied")
    }

    // ...
}
Conditions

There are a couple of conditions available:

Examples

Let's assume that we are using the policy from above for the following requests.

Subject mismatch

This request will fail, because the subject "attacker" does not match []string{"max", "peter", "<zac|ken>"} and since no other policy is given, the request will be denied.

import "github.com/ory-am/ladon"

func main() {
    // ...

    if err := warden.IsAllowed(&ladon.Request{
        Subject: "attacker",
        Action: "delete",
        Resource: "myrn:some.domain.com:resource:123",
    }); err != nil { // this will be true
        log.Fatal("Access denied")
    }

    // ...
}
Owner mismatch

Although the subject "ken" matches []string{"max", "peter", "<zac|ken>"} the request will fail because ken is not the owner of myrn:some.domain.com:resource:123 (peter is).

import "github.com/ory-am/ladon"

func main() {
    // ...

    if err := warden.IsAllowed(&ladon.Request{
        Subject: "ken",
        Action: "delete",
        Resource: "myrn:some.domain.com:resource:123",
        Context: &ladon.Context{
            "resourceOwner": "peter",
        },
    }); err != nil {
        log.Print("Access denied")
    }

    // ...
}
IP address mismatch

Although the subject "peter" matches []string{"max", "peter", "<zac|ken>"} the request will fail because the "IPMatchesCondition" is not full filled.

import "github.com/ory-am/ladon"

func main() {
    // ...

    if err := warden.IsAllowed(&ladon.Request{
        Subject: "peter",
        Action: "delete",
        Resource: "myrn:some.domain.com:resource:123",
        Context: &ladon.Context{
            "resourceOwner": "peter",
        },
    }); err != nil {
        log.Print("Access denied")
    }

    // ...
}
Working example

This request will be allowed because all requirements are met.

import "github.com/ory-am/ladon"

func main() {
    // ...

    if err := warden.IsAllowed(&ladon.Request{
        Subject: "peter",
        Action: "delete",
        Resource: "myrn:some.domain.com:resource:123",
        Context: ladon.Context{
            "resourceOwner": "peter",
            "remoteIPAddress": "127.0.0.1",
        },
    }); err != nil {
        log.Print("Access denied")
    }

    // ...
}
Full code for working example

To view the example's full code, click here. To run it, call go test -run=TestLadon .

Good to know

  • All checks are case sensitive because subject values could be case sensitive IDs.
  • If ladon.Ladon is not able to match a policy with the request, it will default to denying the request and return an error.

Ladon does not use reflection for matching conditions to their appropriate structs due to security reasons.

Useful commands

Create mocks

mockgen -package internal -destination internal/manager.go github.com/ory-am/ladon Manager

Documentation

Index

Constants

View Source
const AllowAccess = "allow"

AllowAccess should be used as effect for policies that allow access.

View Source
const DenyAccess = "deny"

DenyAccess should be used as effect for policies that deny access.

Variables

View Source
var (
	// ErrForbidden is returned when access is forbidden.
	ErrForbidden = &Error{
		Error: errors.New("Forbidden"),
		Code:  http.StatusForbidden,
	}
)

Functions

func Match

func Match(p Policy, haystack []string, needle string) (bool, error)

Match matches a needle with an array of regular expressions and returns true if a match was found.

Types

type CIDRCondition

type CIDRCondition struct {
	CIDR string `json:"cidr"`
}

CIDRCondition makes sure that the warden requests' IP address is in the given CIDR.

func (*CIDRCondition) Fulfills

func (c *CIDRCondition) Fulfills(value interface{}, _ *Request) bool

Fulfills returns true if the the request is fulfilled by the condition.

func (*CIDRCondition) GetName

func (c *CIDRCondition) GetName() string

GetName returns the condition's name.

type Condition

type Condition interface {
	// GetName returns the condition's name.
	GetName() string

	// Fulfills returns true if the request is fulfilled by the condition.
	Fulfills(interface{}, *Request) bool
}

Condition either do or do not fulfill an access request.

type Conditions

type Conditions map[string]Condition

Conditions is a collection of conditions.

func (Conditions) AddCondition

func (cs Conditions) AddCondition(key string, c Condition)

AddCondition adds a condition to the collection.

func (Conditions) MarshalJSON

func (cs Conditions) MarshalJSON() ([]byte, error)

MarshalJSON marshals a list of conditions to json.

func (Conditions) UnmarshalJSON

func (cs Conditions) UnmarshalJSON(data []byte) error

UnmarshalJSON unmarshals a list of conditions from json.

type Context

type Context map[string]interface{}

Context is used as request's context.

type DefaultPolicy

type DefaultPolicy struct {
	ID          string     `json:"id" gorethink:"id"`
	Description string     `json:"description" gorethink:"description"`
	Subjects    []string   `json:"subjects" gorethink:"subjects"`
	Effect      string     `json:"effect" gorethink:"effect"`
	Resources   []string   `json:"resources" gorethink:"resources"`
	Actions     []string   `json:"actions" gorethink:"actions"`
	Conditions  Conditions `json:"conditions" gorethink:"conditions"`
}

DefaultPolicy is the default implementation of the policy interface.

func (*DefaultPolicy) AllowAccess

func (p *DefaultPolicy) AllowAccess() bool

AllowAccess returns true if the policy effect is allow, otherwise false.

func (*DefaultPolicy) GetActions

func (p *DefaultPolicy) GetActions() []string

GetActions returns the policies actions.

func (*DefaultPolicy) GetConditions

func (p *DefaultPolicy) GetConditions() Conditions

GetConditions returns the policies conditions.

func (*DefaultPolicy) GetDescription

func (p *DefaultPolicy) GetDescription() string

GetDescription returns the policies description.

func (*DefaultPolicy) GetEffect

func (p *DefaultPolicy) GetEffect() string

GetEffect returns the policies effect which might be 'allow' or 'deny'.

func (*DefaultPolicy) GetEndDelimiter

func (p *DefaultPolicy) GetEndDelimiter() byte

GetEndDelimiter returns the delimiter which identifies the end of a regular expression.

func (*DefaultPolicy) GetID

func (p *DefaultPolicy) GetID() string

GetID returns the policies id.

func (*DefaultPolicy) GetResources

func (p *DefaultPolicy) GetResources() []string

GetResources returns the policies resources.

func (*DefaultPolicy) GetStartDelimiter

func (p *DefaultPolicy) GetStartDelimiter() byte

GetStartDelimiter returns the delimiter which identifies the beginning of a regular expression.

func (*DefaultPolicy) GetSubjects

func (p *DefaultPolicy) GetSubjects() []string

GetSubjects returns the policies subjects.

func (*DefaultPolicy) UnmarshalJSON

func (p *DefaultPolicy) UnmarshalJSON(data []byte) error

type EqualsSubjectCondition

type EqualsSubjectCondition struct{}

SubjectIsOwnerCondition is a condition which is fulfilled if the subject of a warden request is also the owner of the requested resource.

func (*EqualsSubjectCondition) Fulfills

func (c *EqualsSubjectCondition) Fulfills(value interface{}, r *Request) bool

Fulfills returns true if the the request is fulfilled by the condition.

func (*EqualsSubjectCondition) GetName

func (c *EqualsSubjectCondition) GetName() string

GetName returns the condition's name.

type Error

type Error struct {
	*errors.Error

	// Code is the error's http status code.
	Code int
}

Error is an error object.

type Ladon

type Ladon struct {
	Manager Manager
}

Ladon is an implementation of Warden.

func (*Ladon) IsAllowed

func (g *Ladon) IsAllowed(r *Request) (err error)

IsAllowed returns nil if subject s has permission p on resource r with context c or an error otherwise.

type Manager

type Manager interface {

	// Create persists the policy.
	Create(policy Policy) error

	// Get retrieves a policy.
	Get(id string) (Policy, error)

	// Delete removes a policy.
	Delete(id string) error

	// Finds all policies associated with the subject.
	FindPoliciesForSubject(subject string) (Policies, error)
}

Manager is responsible for managing and persisting policies.

type MemoryManager

type MemoryManager struct {
	Policies map[string]Policy
}

Manager is a in-memory implementation of Manager.

func NewMemoryManager

func NewMemoryManager() *MemoryManager

func (*MemoryManager) Create

func (m *MemoryManager) Create(policy Policy) error

func (*MemoryManager) Delete

func (m *MemoryManager) Delete(id string) error

Delete removes a policy.

func (*MemoryManager) FindPoliciesForSubject

func (m *MemoryManager) FindPoliciesForSubject(subject string) (Policies, error)

Finds all policies associated with the subject.

func (*MemoryManager) Get

func (m *MemoryManager) Get(id string) (Policy, error)

Get retrieves a policy.

type Policies

type Policies []Policy

Policies is an array of policies.

type Policy

type Policy interface {
	// GetID returns the policies id.
	GetID() string

	// GetDescription returns the policies description.
	GetDescription() string

	// GetSubjects returns the policies subjects.
	GetSubjects() []string

	// AllowAccess returns true if the policy effect is allow, otherwise false.
	AllowAccess() bool

	// GetEffect returns the policies effect which might be 'allow' or 'deny'.
	GetEffect() string

	// GetResources returns the policies resources.
	GetResources() []string

	// GetActions returns the policies actions.
	GetActions() []string

	// GetConditions returns the policies conditions.
	GetConditions() Conditions

	// GetStartDelimiter returns the delimiter which identifies the beginning of a regular expression.
	GetStartDelimiter() byte

	// GetEndDelimiter returns the delimiter which identifies the end of a regular expression.
	GetEndDelimiter() byte
}

Policy represent a policy model.

type PostgresManager

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

Manager is a postgres implementation of Manager.

func NewPostgresManager

func NewPostgresManager(db *sql.DB) *PostgresManager

func (*PostgresManager) Create

func (s *PostgresManager) Create(policy Policy) (err error)

func (*PostgresManager) CreateSchemas

func (s *PostgresManager) CreateSchemas() error

func (*PostgresManager) Delete

func (s *PostgresManager) Delete(id string) error

func (*PostgresManager) FindPoliciesForSubject

func (s *PostgresManager) FindPoliciesForSubject(subject string) (policies Policies, err error)

func (*PostgresManager) Get

func (s *PostgresManager) Get(id string) (Policy, error)

type Request

type Request struct {
	// Resource is the resource that access is requested to.
	Resource string `json:"resource"`

	// Action is the action that is requested on the resource.
	Action string `json:"action"`

	// Subejct is the subject that is requesting access.
	Subject string `json:"subject"`

	// Context is the request's environmental context.
	Context Context `json:"context"`
}

Request is the warden's request object.

type RethinkManager

type RethinkManager struct {
	Session *r.Session
	Table   r.Term
	sync.RWMutex

	Policies map[string]Policy
}

func (*RethinkManager) ColdStart

func (m *RethinkManager) ColdStart() error

func (*RethinkManager) Create

func (m *RethinkManager) Create(policy Policy) error

func (*RethinkManager) Delete

func (m *RethinkManager) Delete(id string) error

Delete removes a policy.

func (*RethinkManager) FindPoliciesForSubject

func (m *RethinkManager) FindPoliciesForSubject(subject string) (Policies, error)

Finds all policies associated with the subject.

func (*RethinkManager) Get

func (m *RethinkManager) Get(id string) (Policy, error)

Get retrieves a policy.

func (*RethinkManager) Watch

func (m *RethinkManager) Watch(ctx context.Context) error

type StringEqualCondition

type StringEqualCondition struct {
	Equals string `json:"equals"`
}

SubjectIsOwnerCondition is a condition which is fulfilled if the subject of a warden request is also the owner of the requested resource.

func (*StringEqualCondition) Fulfills

func (c *StringEqualCondition) Fulfills(value interface{}, _ *Request) bool

Fulfills returns true if the the request is fulfilled by the condition.

func (*StringEqualCondition) GetName

func (c *StringEqualCondition) GetName() string

GetName returns the condition's name.

type Warden

type Warden interface {
	// IsAllowed returns nil if subject s can perform action a on resource r with context c or an error otherwise.
	//  if err := guard.IsAllowed(&Request{Resource: "article/1234", Action: "update", Subject: "peter"}); err != nil {
	//    return errors.New("Not allowed")
	//  }
	IsAllowed(r *Request) error
}

Warden is responsible for deciding if subject s can perform action a on resource r with context c.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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