ory / ladon

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

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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

RafPe opened this issue · comments

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

I like this! We can also add additional dimensions - so not just passing the ID but for example the policy as a whole!

Maybe have three methods - one is for deny, one is for allow, and one is for default (deny, when no policies are found to be matching)?

I think I would keep one method for processing and let the client implementing the framework to make decision about the split.
Saying this I really like the idea of passing the whole policy object as the dimension.

So our interface would then look as

type LadonMetric interface {
	Process(Policy, string)
}

I will create a branch and will start work on it ;)

I think I would keep one method for processing and let the client implementing the framework to make decision about the split.

You're already implicitly implying that by passing a second argument, "string" which probably is some type of enum (and thus we make the decision about what the process is). Having said that I'm happy to wait for the first implementation, maybe it really is enough! :)

It'd probably make sense to run this as a go routine, so make sure to pass a copy of the policy, not a pointer (Policy as a function argument is fine)!

You're already implicitly implying that by passing a second argument, "string" which probably is some type of enum (and thus we make the decision about what the process is). Having said that I'm happy to wait for the first implementation, maybe it really is enough! :)

I have thought about having this addressed in proper way. Hence in my initial PR I have added the following const which I also use

https://github.com/RafPe/ladon/blob/master/const.go#L30
// NoMatchingPolicy should be used for metrics when no policy has been found. const NoMatchingPolicy = "noMatchingPolicy"

I'm not going to be pushing into using one method if we would see benefit of split. Would you have any suggestions ? Maybe something like

type LadonMetric interface {
	Denied(Policy)
        Allowed(Policy)
        NoMatch(Policy)
}
  • edited and removed the string param since functions are self indicating of what was the result

It'd probably make sense to run this as a go routine, so make sure to pass a copy of the policy, not a pointer (Policy as a function argument is fine)!

Could you maybe give me some hints how would you see us achieve this ?

I'm not going to be pushing into using one method if we would see benefit of split. Would you have any suggestions ? Maybe something like

Yup looks pretty good! Maybe we could add one more word to make it even more explicit / extendable:

type LadonMetric interface {
	RequestDeniedBy(Request, Policy)
        RequestAllowedBy(Request, Policy)
        RequestNoMatch(Request) // <- no match also means no policy!
}

Could you maybe give me some hints how would you see us achieve this ?

Yes, actually not a lot required:

	// 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
		go l.Metrics.Process(p /* make sure this is not a pointer */, strconv.FormatBool(p.AllowAccess()))

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

cool - let me work on that one 👍