Giter Site home page Giter Site logo

ory / ladon Goto Github PK

View Code? Open in Web Editor NEW
2.4K 63.0 226.0 1.22 MB

A SDK for access control policies: authorization for the microservice and IoT age. Inspired by AWS IAM policies. Written for Go.

Home Page: https://www.ory.sh/?utm_source=github&utm_medium=banner&utm_campaign=ladon

License: Apache License 2.0

Go 100.00%

ladon's Introduction

ORY Ladon - Policy-based Access Control

Join the chat at https://www.ory.sh/chat Join newsletter

Build Status Coverage Status Go Report Card GoDoc

Ladon is the serpent dragon protecting your resources.

Ladon is a library written in Go for access control policies, similar to Role Based Access Control or Access Control Lists. In contrast to ACL and RBAC you get fine-grained access control with the ability to answer questions in complex environments such as multi-tenant or distributed applications and large organizations. Ladon is inspired by AWS IAM Policies.

Ladon officially ships with an exemplary in-memory storage implementations. Community-supported adapters are available for CockroachDB.

Ladon is now considered stable.


ORY builds solutions for better internet security and accessibility. We have a couple more projects you might enjoy:

  • Hydra, a security-first open source OAuth2 and OpenID Connect server for new and existing infrastructures that uses Ladon for access control.
  • ORY Editor, an extensible, modern WYSI editor for the web written in React.
  • Fosite, an extensible security first OAuth 2.0 and OpenID Connect SDK for Go.
  • Dockertest: Write better integration tests with dockertest!

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

This library works with Go 1.11+.

export GO111MODULE=on
go get github.com/ory/ladon

Ladon uses semantic versioning and versions beginning with zero (0.1.2) might introduce backwards compatibility breaks with each minor version.

Concepts

Ladon is an access control library that answers the question:

Who is able to do what on something given some context

  • Who: An arbitrary unique subject name, for example "ken" or "printer-service.mydomain.com".
  • Able: The effect which can be either "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 containing information about the environment such as the IP Address, request date, the resource owner name, the department ken is working in or any other information you want to pass along. (optional)

To decide what the answer is, Ladon uses policy documents which can be represented as JSON

{
  "description": "One policy to rule them all.",
  "subjects": ["users:<peter|ken>", "users:maria", "groups:admins"],
  "actions" : ["delete", "<create|update>"],
  "effect": "allow",
  "resources": [
    "resources:articles:<.*>",
    "resources:printer"
  ],
  "conditions": {
    "remoteIP": {
        "type": "CIDRCondition",
        "options": {
            "cidr": "192.168.0.1/16"
        }
    }
  }
}

and can answer access requests that look like:

{
  "subject": "users:peter",
  "action" : "delete",
  "resource": "resources:articles:ladon-introduction",
  "context": {
    "remoteIP": "192.168.0.5"
  }
}

However, Ladon does not come with a HTTP or server implementation. It does not restrict JSON either. We believe that it is your job to decide if you want to use Protobuf, RESTful, HTTP, AMQP, or some other protocol. It's up to you to write the server!

The following example should give you an idea what a RESTful flow could look like. Initially we create a policy by POSTing it to an artificial HTTP endpoint:

> curl \
      -X POST \
      -H "Content-Type: application/json" \
      -d@- \
      "https://my-ladon-implementation.localhost/policies" <<EOF
        {
          "description": "One policy to rule them all.",
          "subjects": ["users:<peter|ken>", "users:maria", "groups:admins"],
          "actions" : ["delete", "<create|update>"],
          "effect": "allow",
          "resources": [
            "resources:articles:<.*>",
            "resources:printer"
          ],
          "conditions": {
            "remoteIP": {
                "type": "CIDRCondition",
                "options": {
                    "cidr": "192.168.0.1/16"
                }
            }
          }
        }
  EOF

Then we test if "peter" (ip: "192.168.0.5") is allowed to "delete" the "ladon-introduction" article:

> curl \
      -X POST \
      -H "Content-Type: application/json" \
      -d@- \
      "https://my-ladon-implementation.localhost/warden" <<EOF
        {
          "subject": "users:peter",
          "action" : "delete",
          "resource": "resources:articles:ladon-introduction",
          "context": {
            "remoteIP": "192.168.0.5"
          }
        }
  EOF

{
    "allowed": true
}

Usage

We already discussed two essential parts of Ladon: policies and access control requests. Let's take a closer look at those two.

Policies

Policies are the basis for access control decisions. Think of them as a set of rules. In this library, policies are abstracted as the ladon.Policy interface, and Ladon comes with a standard implementation of this interface which is ladon.DefaultPolicy. Creating such a policy could look like:

import "github.com/ory/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:<.+>", "myrn:some.domain.com:resource:<(?!protected).*>",
            "myrn:some.domain.com:resource:<[[:digit:]]+>",
        },

	// 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",
		},
	},
}

Conditions

Conditions are functions returning true or false given a context. Because conditions implement logic, they must be programmed. Adding conditions to a policy consist of two parts, a key name and an implementation of ladon.Condition:

// StringEqualCondition is an exemplary condition.
type StringEqualCondition struct {
	Equals string `json:"equals"`
}

// Fulfills returns true if the given value is a string and is the
// same as in StringEqualCondition.Equals
func (c *StringEqualCondition) Fulfills(value interface{}, _ *ladon.Request) bool {
	s, ok := value.(string)

	return ok && s == c.Equals
}

// GetName returns the condition's name.
func (c *StringEqualCondition) GetName() string {
	return "StringEqualCondition"
}

var pol = &ladon.DefaultPolicy{
    // ...
    Conditions: ladon.Conditions{
        "some-arbitrary-key": &StringEqualCondition{
            Equals: "the-value-should-be-this"
        }
    },
}

The default implementation of Policy supports JSON un-/marshalling. In JSON, this policy would look like:

{
  "conditions": {
    "some-arbitrary-key": {
        "type": "StringEqualCondition",
        "options": {
            "equals": "the-value-should-be-this"
        }
    }
  }
}

As you can see, type is the value that StringEqualCondition.GetName() is returning and options is used to set the value of StringEqualCondition.Equals.

This condition is fulfilled by (we will cover the warden in the next section)

var err = warden.IsAllowed(&ladon.Request{
    // ...
    Context: ladon.Context{
        "some-arbitrary-key": "the-value-should-be-this",
    },
}

but not by

var err = warden.IsAllowed(&ladon.Request{
    // ...
    Context: ladon.Context{
        "some-arbitrary-key": "some other value",
    },
}

and neither by:

var err = warden.IsAllowed(&ladon.Request{
    // ...
    Context: ladon.Context{
        "same value but other key": "the-value-should-be-this",
    },
}

Ladon ships with a couple of default conditions:

The CIDR condition matches CIDR IP Ranges. Using this condition would look like this in JSON:

{
    "conditions": {
        "remoteIPAddress": {
            "type": "CIDRCondition",
            "options": {
                "cidr": "192.168.0.1/16"
            }
        }
    }
}

and in Go:

var pol = &ladon.DefaultPolicy{
    Conditions: ladon.Conditions{
        "remoteIPAddress": &ladon.CIDRCondition{
            CIDR: "192.168.0.1/16",
        },
    },
}

In this case, we expect that the context of an access request contains a field "remoteIpAddress" matching the CIDR "192.168.0.1/16", for example "192.168.0.5".

Checks if the value passed in the access request's context is identical with the string that was given initially

var pol = &ladon.DefaultPolicy{
    Conditions: ladon.Conditions{
        "some-arbitrary-key": &ladon.StringEqualCondition{
            Equals: "the-value-should-be-this"
        }
    },
}

and would match in the following case:

var err = warden.IsAllowed(&ladon.Request{
    // ...
    Context: ladon.Context{
         "some-arbitrary-key": "the-value-should-be-this",
    },
}

Checks if the boolean value passed in the access request's context is identical with the expected boolean value in the policy

var pol = &ladon.DefaultPolicy{
    Conditions: ladon.Conditions{
        "some-arbitrary-key": &ladon.BooleanCondition{
            BooleanValue: true,
        }
    },
}

and would match in the following case:

var err = warden.IsAllowed(&ladon.Request{
    // ...
    Context: ladon.Context{
        "some-arbitrary-key": true,
    },
})

This condition type is particularly useful if you need to assert a policy dynamically on resources for multiple subjects. For example, consider if you wanted to enforce policy that only allows individuals that own a resource to view that resource. You'd have to be able to create a Ladon policy that permits access to every resource for every subject that enters your system.

With the Boolean Condition type, you can use conditional logic at runtime to create a match for a policy's condition.

Checks if the value passed in the access request's context matches the regular expression that was given initially

var pol = &ladon.DefaultPolicy{
    Conditions: ladon.Conditions{
      "some-arbitrary-key": &ladon.StringMatchCondition{
          Matches: "regex-pattern-here.+"
      }
    }
}

and would match in the following case:

var err = warden.IsAllowed(&ladon.Request{
    // ...
    Context: ladon.Context{
          "some-arbitrary-key": "regex-pattern-here111"
    }
  }
})

Checks if the access request's subject is identical with the string that was given initially

var pol = &ladon.DefaultPolicy{
    Conditions: ladon.Conditions{
        "some-arbitrary-key": &ladon.EqualsSubjectCondition{}
    },
}

and would match

var err = warden.IsAllowed(&ladon.Request{
    // ...
    Subject: "peter",
    Context: ladon.Context{
         "some-arbitrary-key": "peter",
    },
}

but not:

var err = warden.IsAllowed(&ladon.Request{
    // ...
    Subject: "peter",
    Context: ladon.Context{
         "some-arbitrary-key": "max",
    },
}

Checks if the value passed in the access request's context contains two-element arrays and that both elements in each pair are equal.

var pol = &ladon.DefaultPolicy{
    Conditions: ladon.Conditions{
        "some-arbitrary-key": &ladon.StringPairsEqualCondition{}
    },
}

and would match

var err = warden.IsAllowed(&ladon.Request{
    // ...
    Context: ladon.Context{
         "some-arbitrary-key": [
             ["some-arbitrary-pair-value", "some-arbitrary-pair-value"],
             ["some-other-arbitrary-pair-value", "some-other-arbitrary-pair-value"],
         ]
    },
}

but not:

var err = warden.IsAllowed(&ladon.Request{
    // ...
    Context: ladon.Context{
         "some-arbitrary-key": [
             ["some-arbitrary-pair-value", "some-other-arbitrary-pair-value"],
         ]
    },
}

Checks if the string value passed in the access request's context is present in the resource string.

The Condition requires a value string and an optional delimiter (needs to match the resource string) to be passed.

A resource could for instance be: myrn:some.domain.com:resource:123 and myrn:some.otherdomain.com:resource:123 (the : is then considered a delimiter, and used by the condition to be able to separate the resource components from each other) to allow an action to the resources on myrn:some.otherdomain.com you could for instance create a resource condition with

{value: myrn:some.otherdomain.com, Delimiter: ":"}

alternatively:

{value: myrn:some.otherdomain.com}

The delimiter is optional but needed for the condition to be able to separate resource string components: i.e. to make sure the value foo:bar matches foo:bar but not foo:bara nor foo:bara:baz.

That is, a delimiter is necessary to separate:

{value: "myrn:fo", delimiter: ":"} from {value: "myrn:foo", delimiter: ":"} or {value: "myid:12"} from {value: "myid:123"}.

This condition is fulfilled by this (allow for all resources containing part:north):

var err = warden.IsAllowed(&ladon.Request{
    // ...
    Resource: "rn:city:laholm:part:north"
    Context: ladon.Context{
      delimiter: ":",
      value: "part:north"
    },
}

or ( allow all resources with city:laholm)

var err = warden.IsAllowed(&ladon.Request{
    // ...
    Resource: "rn:city:laholm:part:north"
    Context: ladon.Context{
      delimiter: ":",
      value: "city:laholm"
    },
}

but not (allow for all resources containing part:west, the resource does not contain part:west):

var err = warden.IsAllowed(&ladon.Request{
    // ...
    Resource: "rn:city:laholm:part:north"
    Context: ladon.Context{
      delimiter: ":",
      value: "part:west"
    },
}
Adding Custom Conditions

You can add custom conditions by appending it to ladon.ConditionFactories:

import "github.com/ory/ladon"

func main() {
    // ...

    ladon.ConditionFactories[new(CustomCondition).GetName()] = func() Condition {
        return new(CustomCondition)
    }

    // ...
}

Persistence

Obviously, creating such a policy is not enough. You want to persist it too. Ladon ships an interface ladon.Manager for this purpose. You have to implement that interface for persistence. An exemplary in-memory adapter can be found in ./manager/memory/manager_memory.go:

Let's take a look how to instantiate those:

In-Memory (officially supported)

import (
	"github.com/ory/ladon"
	manager "github.com/ory/ladon/manager/memory"
)


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

    // ...
}

Access Control (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/ladon"

func main() {
    // ...

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

    // ...
}

Audit Log (Warden)

In order to keep track of authorization grants and denials, it is possible to attach a ladon.AuditLogger. The provided ladon.AuditLoggerInfo outputs information about the policies involved when responding to authorization requests.

import "github.com/ory/ladon"
import manager "github.com/ory/ladon/manager/memory"

func main() {

    warden := ladon.Ladon{
        Manager: manager.NewMemoryManager(),
        AuditLogger: &ladon.AuditLoggerInfo{}
    }

    // ...

It will output to stderr by default.

Metrics

Ability to track authorization grants,denials and errors, it is possible to implement own interface for processing metrics.

type prometheusMetrics struct{}

func (mtr *prometheusMetrics) RequestDeniedBy(r ladon.Request, p ladon.Policy) {}
func (mtr *prometheusMetrics) RequestAllowedBy(r ladon.Request, policies ladon.Policies) {}
func (mtr *prometheusMetrics) RequestNoMatch(r ladon.Request) {}
func (mtr *prometheusMetrics) RequestProcessingError(r ladon.Request, err error) {}

func main() {

    warden := ladon.Ladon{
        Manager: manager.NewMemoryManager(),
        Metric:  &prometheusMetrics{},
    }

    // ...

Limitations

Ladon's limitations are listed here.

Regular expressions

Matching regular expressions has a complexity of O(n) (except lookahead/lookbehind assertions) and databases such as MySQL or Postgres can not leverage indexes when parsing regular expressions. Thus, there is considerable overhead when using regular expressions.

We have implemented various strategies for reducing policy matching time:

  1. An LRU cache is used for caching frequently compiled regular expressions. This reduces cpu complexity significantly for memory manager implementations.
  2. The SQL schema is 3NF normalized.
  3. Policies, subjects and actions are stored uniquely, reducing the total number of rows.
  4. Only one query per look up is executed.
  5. If no regular expression is used, a simple equal match is done in SQL back-ends.

You will get the best performance with the in-memory manager. The SQL adapters perform about 1000:1 compared to the in-memory solution. Please note that these tests where in laboratory environments with Docker, without an SSD, and single-threaded. You might get better results on your system. We are thinking about introducing simple cache strategies such as LRU with a maximum age to further reduce runtime complexity.

We are also considering to offer different matching strategies (e.g. wildcard match) in the future, which will perform better with SQL databases. If you have ideas or suggestions, leave us an issue.

Examples

Check out ladon_test.go which includes a couple of policies and tests cases. You can run the code with go test -run=TestLadon -v .

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 considerations.

Useful commands

Create mocks

mockgen -package ladon_test -destination manager_mock_test.go github.com/ory/ladon Manager

Third Party Libraries

By implementing the warden.Manager it is possible to create your own adapters to persist data in a datastore of your choice. Below are a list of third party implementations.

ladon's People

Contributors

115100 avatar adhicoder avatar aeneasr avatar arekkas avatar ayzu avatar cabrel avatar cemremengu avatar ericalandouglas avatar f21 avatar hiendv avatar janekolszak avatar jfcurran avatar jon-whit avatar joshuaslate avatar julienbreux avatar kevgo avatar leetal avatar leplatrem avatar loong avatar marcosvidolin avatar maxikg avatar olivierdeckers avatar omeid avatar pinders avatar pnegahdar avatar rafpe avatar shawnps avatar ugodiggi avatar zerodivisi0n avatar zikes avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ladon's Issues

Status of the package

What feature status if this repo?
I see in commits that sometimes ago repo goes to archive, but after that info is removed from readme. Do you plan to maintain it?
I need to know, to decide which package use , this or casbin

Can't get integration tests working

The only way I can get the tests running, is by commenting out connectRDB() in TestMain.

connectRDB() results in the following fatal error: 2017/02/26 13:57:18 Could not start resource: : API error (500): {"message":"invalid environment variable: "}

connectMySQL() also prints the following (non-fatal) error message repeatedly: [mysql] 2017/02/26 14:00:09 packets.go:33: unexpected EOF

My Docker version is: 1.13.1, build 092cba3 (OSX)
I run the test using go test -v . after installing the dependencies using glide install

ladon_policy table not getting created

When I use the manager to interface with sql, the required tables are not getting created.

db, err := sqlx.Open("mysql", "tx81:@tcp(127.0.0.1:3306)/policies")
......
err=db.Ping()
if err == nil {
fmt.Printf("Database is up")
}

warden := ladon.Ladon{
    Manager: manager.NewSQLManager(db, nil),
}

    var pol = &ladon.DefaultPolicy{
        ......
}
err = warden.Manager.Create(pol)
fmt.Printf("%s", err)

The error is printed as "Table 'policies.ladon_policy' doesn't exist".

Multiple/secondary request subjects?

For our use case, it would be highly beneficial to be able to send multiple subjects as part of the same request, e.g. "subjects": ["user:peter", "group:admin"] to avoid having to query the API multiple times.

I'm more than happy to provide a PR for this, just wanted to check first of there are any reservations or concerns?

Cheers,
dim

Example with multiple conditions missing

https://github.com/ory/ladon/blob/9fada03c11c183e37c13f581ee6deca8d8e747f9/manager_test_helper.go

Looking at the manager I see the:

	{
		ID:          uuid.New(),
		Description: "description",
		Subjects:    []string{},
		Effect:      AllowAccess,
		Resources:   []string{"foo"},
		Actions:     []string{},
		Conditions:  Conditions{},
	}

Should not the conditions be an array, for example: I may want to evaluate both the CIDR-address and string match on something.

tl;dr; Conditions is plural in the struct but is not a slice...

sqlx requirement for sql manager

In the documentation (https://github.com/ory/ladon#persistence) the SQL example shows a database connection created using stdlib's sql.Open, however the ladon/manager/sql NewSQLManager function lists the first argument as db *sqlx.DB. As a result, when a standard *sql.DB is passed in, this error is given:

cannot use ladonDb (type *"database/sql".DB) as type *sqlx.DB in argument to "github.com/ory/ladon/manager/sql".NewSQLManager

feature-request: expose info which policy decided on authz result

Do you want to request a feature or report a bug?
feature

What is the current behavior?
Currently ladon framework does not expose in any way information from isAllowed which policy did allow or deny the request.

What is the expected behavior?
I would expect to be able to have that information available from the framework.

Which version of the software is affected?
not applicable yet


Opening this as an issue to discuss initially potential approaches. My goal would be to be able to have metrics from the framework regarding policy which denied/allowed request.

So one initial idea could be extending ladon with the following piece of code

// LadonMetric is used to pass metrics function ( in this example I will just take policyID and resultof authz ) 
type LadonMetric interface {
	Process(string, string)
}

// Ladon is an implementation of Warden.
type Ladon struct {
	Manager     Manager
	Matcher     matcher
	AuditLogger AuditLogger
	Metrics      LadonMetric
}

// DoPoliciesAllow returns nil if subject s has permission p on resource r with context c for a given policy list or an error otherwise.
// The IsAllowed interface should be preferred since it uses the manager directly. This is a lower level interface for when you don't want to use the ladon manager.
func (l *Ladon) DoPoliciesAllow(r *Request, policies []Policy) (err error) {
	var allowed = false
	var deciders = Policies{}

	// Iterate through all policies
	for _, p := range policies {

               // At the stage of making decision if we allow or not we would call the func defined by interface
		l.Metrics.Process(p.GetID(), strconv.FormatBool(p.AllowAccess()))

              // ...... code removed for readability 

Then within the client app we would create the following

// Client app create custom type matching interface signature from Ladon
type prometheusMetrics struct{}

func (s *prometheusMetrics) Process(policyId string, result string) {
	// dummy print of results
	logger.Printf("yey from %s and %s", policyId, result)
}

	// init warden and pass the custom metrics type
	warden = &ladon.Ladon{
		Metrics: &prometheusMetrics{},
		Manager: manager.NewMemoryManager(),
		AuditLogger: &ladon.AuditLoggerInfo{
			Logger: policyLogs,
		},
	}

From the above we would be able to consume metrics.

I would like to point out this is just first idea so I'm open for input.

And if we get a consent that this would work out - I would be more than happy to do a PR

MemoryManager.GetAll() return random policies

If I call memory.GetAll(10,0), every time it return different data.

func (m *MemoryManager) GetAll(limit, offset int64) (Policies, error) {

so I think this function should be sort first.

        var keys []string
        m. RLock()  
        start, end := pagination.Index(int(limit), int(offset), len(m.Policies))
	for key, p := range m.Policies {
                keys = append(keys, key)
	}
        sort.Ints(keys)
        var rst []Policies
         for _, key := range keys[start:end] {
                  rst = append(rst, m.Policies[key])
        }
        m. RUnlock()
	return rst, nil

New custom StringPairsEqual condition rejects valid input

When using the most recent version of ladon (which includes the new StringPairsEqualCondition) in a fork of Hydra, the warden was incorrectly denying requests when given valid contexts.

A sample policy to be tested against:

{
  "description": "Allow account admins full access to account resources.",
  "subjects": [
    "account-admin"
  ],
  "effect": "allow",
  "resources": [
    "rn:accounts:<[A-z0-9_-]+(:.+)?>"
  ],
  "actions": [
    "create", "read", "update", "delete"
  ],
  "conditions": {
    "account-ids": {
      "type": "StringPairsEqualCondition"
    }
  }
}

A sample context to test against:

{
  "resource": "rn:accounts:1",
  "action": "read",
  "subject": "<SUBJECT_WITH_ABOVE_POLICY>",
  "context": {
    "account-ids": [
      ["1", "1"]
    ]
  }
}

I'm wondering if this can be recreated by others. I believe the following line is failing to coerce properly, https://github.com/ory/ladon/blob/master/condition_string_pairs_equal.go#L11.

I am currently using the following implementation for this condition's Fufills function:

func (c *StringPairsEqualCondition) Fulfills(value interface{}, _ *Request) bool {
  pairs, PairsOk := value.([]interface{})

  if PairsOk {
    for _, v := range pairs {
      pair, PairOk := v.([]interface{})
      if !PairOk || (len(pair) != 2) {
        return false
      }

      a, AOk := pair[0].(string)
      b, BOk := pair[1].(string)

      if !AOk || !BOk || (a != b) {
        return false
      }
    }
    return true
  }

  return false
}

It seems Hydra was able to process the type coercion []interface{} but not [][]interface{}. I originally had a similar implementation to the above function's but I had to rework the type coercing to get tests to pass. Now Hydra seems upset :(

Not sure if this is because Hydra is doing something with the input or if it is ladon specific so I created the issue here.

Great product, Several questions

First off, huge props to the ory-am team behind Ladon. You guys are putting together a great product and I hope to contribute in the future. I came across Ladon while looking for authentication and authorization solutions for a new product at work. Originally I was considering Hydra, but Oauth seemed too bloated and complicated for the first few versions. We decided to use JWTs for authentication because they're simple and stateless. However, authorization is still up in the air and we're leaning towards Ladon, but we have a few questions before we get cooking and start rolling out tests with Ladon.

How production ready is Ladon in its current state? Are there any big changes planned for Ladon in the next year?

Is containerizing possible with AWS Elasticache persistance? I plan to containerize Ladon and do a little rewriting to store policies in AWS Elasticache instead of the machines/containers memory.

Does Ladon allow policy variables, similar to AWS? If not, how difficult would it be to implement? (http://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_variables.html) The goal of policy variables is to avoid the need to create a policy for every new user or resource.

Are there any examples with a REST request endpoint besides the curl script? With my understanding of the codebase, it should be as easy as defining a POST endpoint that implements Warden.

Forgive me if this write up is a little long. I would much rather ask the questions now before I invest several hours into understanding and forking the repo.

Excited to dive deeper into the possibilities of Ladon! Huge thank you to anyone who is happy to respond.

Error: Transaction has already been committed or rolled back

Hi there,
We're using CockroachDB (which is wire-compatible with postgres). Whenever we call sqlmanager.Create(...), we get the error:

2017/10/31 15:58:16 sql: Transaction has already been committed or rolled back

It's a bit hard for us to track down. We think the transaction may be failing / rolled back because of the structure of the prepared statements, but we're not totally sure.

I'll try to test it on postgres here in a few hours.

Question: Thread-safety?

It appears to me that the library might be thread-safe if the storage being used is thread-safe, as all of the operations as far as I can tell boil down to static logic and storage manipulations (which at least for the case of sqlx.DB are thread-safe).

I'm not entirely confident in my own assessment though, and wouldn't trust this without some assurance that matching the thread-safety of the storage implementation is an actual design goal (meaning the library will remain thread-safe in the future).

So what is the library's policy on thread-safety?

Unfinished sentence in README

Hi y'all! I'd open a PR for this, but I'm genuinely unsure as to what the meaning of the last sentence of this paragraph, taken from the README, is supposed to be:

You will get the best performance with the in-memory manager. The SQL adapters perform about 1000:1 compared to the in-memory solution. Please note that these tests where in laboratory environments with Docker, without an SSD, and single-threaded. You might get better results on your system. We are thinking about introducing It would be possible a simple cache strategy such as LRU with a maximum age to further reduce runtime complexity.

Fix 403 issue when policy not found to return 404

When a policy is not found the return error is 403, but should be 404.

That's happening because on DoPoliciesAllow function, the first validation is to check if exist a policy, when that's always false, the loop exit without any error and the variable allowed stay false, so the return is always ErrRequestDenied, which is the 403.

In this case should return ErrNotFound which is 404, for that only need to check if the policy was found.

I made a fix to that on:
https://github.com/RoValentim/ladon/blob/bf9a39ca71cd959a9e9b2228f88fc54f698f49ed/ladon.go#L63

Since CONTRIBUTING.md ask for first discuss it here, I would like to know if it's possible to create a PR for that, or should I make something more to solve this issue?

Update README

The README seems not up to date.

guard.DefaultGuard seems not in use anymore.

And following would throw compile errors:
var pol := &policy.DefaultPolicy{
var guardian := new(guard.DefaultGuard)
(var and := don't go together)

Regarding support for SQL manager

@aeneasr I had used Ladon to build a role-based access management system. I recently came to know that support for the SQL manager has been dropped. Since my use case involves a small number of policies and it is already productionized, is there any way we can continue using the older version which had support for SQL?

I want to install the version 0.8.0, and not 1.0.0. Is there any way I can modify the makefile to install that specific version of Ladon so that it retains support for SQL?

Incorrect examples?

As the examples suggest I am trying:

	accessRequest := &ladon.Request{
		Subject:  "max",
		Resource: "myrn:some.domain.com:resource:123",
		Action:   "update",
		Context: &ladon.Context{
			"some-arbitrary-key": "the-value-should-be-this",
		},
	}

However the compile complains for &ladon.Context saying that it should be ladon.Context. Am I doing something wrong? I can send a PR to fix the examples if that is the case.

Millions of subjects and resources

I've been trying Ladon for a small set of subjects and resources and works great, but I'd like to know if it's intended to be used when the number of subjects and resources are around millions. Does this depend on the manager implementation?

Help required with custom conditions

Apart from appending a custom condition in ladon.conditionFactories, to create a custom condition, a function defining the condition is required. Where is this function defined? Is the file containing this function definition supposed to contain package ladon?
I am not able to include package ladon in the local file. The error message says: can't load package: package .: found packages ladon (arrayEqualsCondition.go) and main (loadPolicies.go) in /Users/tx81/Documents/Shared/PLM_UAM. How is this conflict resolved and how are custom conditions generally defined? The docs are a bit sparse on this one.

Find out matching policy

In the service I'm implementing, I'd like to log which policy allowed the request.

From the following comment, I understand that it is currently not possible:

ladon/ladon.go

Lines 27 to 30 in 4223d97

// Although the manager is responsible of matching the policies, it might decide to just scan for
// subjects, it might return all policies, or it might have a different pattern matching than Golang.
// Thus, we need to make sure that we actually matched the right policies.
return l.DoPoliciesAllow(r, policies)

Would you agree if I contribute a FindMatchingPolicy() on the manager interface? Or do you have a better idea?

Thanks!

Why ladon policy has `effect` field ?

I know that ladon library is inspired by AWS IAM, but i want to learn the reason why we need effect field in Policy struct .

I use AWS IAM daily, but i don't think there are some functions relative to effect field, if i want to deny a request, i just need to remove that policy, em ...... Am i right ?

Attribute Based Control

I'm really interested in adopting ladon as the authorization framework for an API that I am developing in Golang. I'd like to be able to make access control decisions based on not only users and groups permitted to do something, but also if users contain a specific attribute. For example consider the following policy:

{
  "description": "Allow all District Managers to create, update, and delete articles under any conditions.",
  "subjects": ["users: <(user.position == 'District Manager')>"],
  "actions" : ["<create|update|delete>"],
  "effect": "allow",
  "resources": [
    "resources:articles:<.*>",
  ],
}

The subjects in this case can map to all of the users who have an attribute (e.g. position) that is equal to "District Manager".

Is there an easy way to achieve this style of policy definition in ladon already? If not, would you consider adding something like this?

YAML support for policy Conditions

I'm running into several issues when un/marshaling YAML (with "gopkg.in/yaml.v2")

For example, the conditions don't get marshaled at all, and unmarshaling fails with an error like value of type map[interface {}]interface {} is not assignable to type ladon.Condition

I'm a true beginner in Go, but if there's someone to guide me through, I'd be delighted to contribute the necessary changes — unless you consider it out of scope, in which case, we can close this issue :)

Note: It works fine as JSON ;)


package main

import (
  "github.com/ory/ladon"
  "gopkg.in/yaml.v2"
)

func main() {
  var out = []*ladon.DefaultPolicy{
    {
      ID:          "1",
      Description: "description",
      Subjects:    []string{"user"},
      Effect:      ladon.AllowAccess,
      Resources:   []string{"articles:<[0-9]+>"},
      Actions:     []string{"create", "update"},
      Conditions:  ladon.Conditions{
        "owner": &ladon.EqualsSubjectCondition{},
      },
    },
    {
      Effect:     ladon.DenyAccess,
      Conditions: make(ladon.Conditions),
    },
  }
  yamlData, err := yaml.Marshal(out)
  if err != nil {
    panic(err)
  }
  println(string(yamlData))

  var asYAML []*ladon.DefaultPolicy
  if err := yaml.Unmarshal(yamlData, &asYAML); err != nil {
    panic(err)
  }
}

How does ladon verify the scope/hierarchy or tenancy of the resource in an incoming request ?

Take a multi-tenanted scenario, with multiple spaces(or projects) per tenant.
If i have the following policy defined

{
  "description": "Sample policy.",
  "subjects": ["users:<peter|ken>", "users:maria", "groups:admins"],
  "actions" : ["delete", "<create|update>"],
  "effect": "allow",
  "resources": [
    "resources:myorg.com:organizations:dummyorg:spaces:testspace:<.+>"
  ]
}

and the following request comes in

{
  "subject": "users:peter",
  "action" : "delete",
  "resource": "resources:myorg.com:organizations:dummyorg:spaces:testspace:resource123",
}

Is ladon able to verify that resource123 actually sits under organizations:dummyorg and spaces:testspace ?

Ladon Manager datastore future issues?

-- More of a question

Was there a fundamental problem or limitation with the Ladon framework that forced the Keto server to replace it with the OPA Rego system? Or, was it just a question of easier achieving the desired goals with OPA?

I want to use the Ladon framework in a somewhat specialized way and was wondering if I am wasting my time because there's some major issue with it. The Keto issue that raised the first problem with Ladon cited an issue with fast writes to the policy creations caused conflicts, can this be (by myself obviously) resolved by implementing an appropriate ladon.Manager?s

Import of testing package

You import the testing package in the manager_test_helper.go file

As a result any program which is using the flag standard package and depends on the ladon library with produce many extra flags:

-test.bench string
        regular expression to select benchmarks to run
-test.benchmem
        print memory allocations for benchmarks
-test.benchtime duration
        approximate run time for each benchmark (default 1s)
-test.blockprofile string
        write a goroutine blocking profile to the named file after execution

Please slip the testing code from the production one

Issue with resource naming

I came across a peculiar issue with Ladon. One of the resources I used was name "Product:<.*>" It worked fine. But on renaming the resource to "product:<.*> ", the resource is not getting appended to the resources array. All other variations like "product:attributeGroup:<.*> ", "product:attributeGroup:attribute" work fine and get appended to the resources array. But on trying to insert"product:<.*> " with a lowercase "p" alone, it fails.
There is an issue if action name is "create" as well. It is only for these particular cases that it fails.
Is this a known bug? Is there a fix?

ladon manager/sql: hard Postres 9.5 dependency

ladon manager/sql uses new Postgres 9.5 upsert feature: 'ON CONFLICT DO NOTHING'.

e.g.:
INSERT INTO ladon_policy (id, description, effect, conditions) VALUES (?, ?, ?, ?) ON CONFLICT DO NOTHING

Because of this we can't bootstrap Hydra with the temporary root client - we use Postgres 9.3 in production and are unable to upgrade in the near-term.

Is there a workaround?
Or could the insert statements that use this feature be amended to make them more database/version agnostic?

Ability to get all policies for given subjects

I want to use ladon to perform authorization for an app developed as a set of microservices.

I will have an Authorization service which allows administrators to configure access policies for each user. However, I want to be able to check whether the user is allowed to perform a certain action on a certain resource within each service. In my case, I want to be able to get the policies for a subject or subjects in my API gateway and then encode them into JSON and pass them along with the requests to other services.

It would be nice if the manager contains a method where we can look up all the policies for a given subject.

Another use-case is if we want to use ladon to implement RBAC. We would have a subject called role:some-role, for example. We also want to provide an interface where administrators can edit all the permissions for a given role. In this use-case, it'd be really helpful if we could ask the manager to give us all the policies where subject equals role:some-role.

Add `Name` field for Policy struct

I'd like to build a IAM system using ory/ladon, but i meet some questions while writing code.

  • The ladon is inspired by AWS IAM policies, so do you think it is suitable to add Name field to Policy struct? In AWS IAM, the Name field is required while add a policy.

  • I has no idea that how to get the set of policies for different accounts, and i can't find a situation to use GetAll method. Do i need to save relationship of policies and users in DB ?

Metadata fields on policies

Hi, I was thinking about adding some more fields to the policies. For example, I may want to trace the origins of a specific policy: when was the policy created, and who created it. What is your opinion about adding these fields?

Thanks

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.