Skip to content

Commit

Permalink
Add optional metrics collector (#142)
Browse files Browse the repository at this point in the history
This PR introduces metrics which can be used to determine which policies deny/allow access.

Functionality is exposed via interface Metric and to be implemented in the code - consuming the framework

Closes #141

Signed-off-by: Rafal Pieniazek rafal@pieniazek.nl
  • Loading branch information
RafPe authored and aeneasr committed Jan 2, 2020
1 parent 4334cd7 commit 0e3588c
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 1 deletion.
23 changes: 23 additions & 0 deletions README.md
Expand Up @@ -52,6 +52,7 @@ ORY builds solutions for better internet security and accessibility. We have a c
- [Persistence](#persistence)
- [Access Control (Warden)](#access-control-warden)
- [Audit Log (Warden)](#audit-log-warden)
- [Metrics](#metrics)
- [Limitations](#limitations)
- [Regular expressions](#regular-expressions)
- [Examples](#examples)
Expand Down Expand Up @@ -669,6 +670,28 @@ func main() {
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.
```go
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.
Expand Down
20 changes: 19 additions & 1 deletion ladon.go
Expand Up @@ -29,6 +29,7 @@ type Ladon struct {
Manager Manager
Matcher matcher
AuditLogger AuditLogger
Metric Metric
}

func (l *Ladon) matcher() matcher {
Expand All @@ -45,10 +46,18 @@ func (l *Ladon) auditLogger() AuditLogger {
return l.AuditLogger
}

func (l *Ladon) metric() Metric {
if l.Metric == nil {
l.Metric = DefaultMetric
}
return l.Metric
}

// IsAllowed returns nil if subject s has permission p on resource r with context c or an error otherwise.
func (l *Ladon) IsAllowed(r *Request) (err error) {
policies, err := l.Manager.FindRequestCandidates(r)
if err != nil {
go l.metric().RequestProcessingError(*r, nil, err)
return err
}

Expand All @@ -66,10 +75,12 @@ func (l *Ladon) DoPoliciesAllow(r *Request, policies []Policy) (err error) {

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

// Does the action match with one of the policies?
// This is the first check because usually actions are a superset of get|update|delete|set
// and thus match faster.
if pm, err := l.matcher().Matches(p, p.GetActions(), r.Action); err != nil {
go l.metric().RequestProcessingError(*r, p, err)
return errors.WithStack(err)
} else if !pm {
// no, continue to next policy
Expand All @@ -80,6 +91,7 @@ func (l *Ladon) DoPoliciesAllow(r *Request, policies []Policy) (err error) {
// There are usually less subjects than resources which is why this is checked
// before checking for resources.
if sm, err := l.matcher().Matches(p, p.GetSubjects(), r.Subject); err != nil {
go l.metric().RequestProcessingError(*r, p, err)
return err
} else if !sm {
// no, continue to next policy
Expand All @@ -88,6 +100,7 @@ func (l *Ladon) DoPoliciesAllow(r *Request, policies []Policy) (err error) {

// Does the resource match with one of the policies?
if rm, err := l.matcher().Matches(p, p.GetResources(), r.Resource); err != nil {
go l.metric().RequestProcessingError(*r, p, err)
return errors.WithStack(err)
} else if !rm {
// no, continue to next policy
Expand All @@ -101,10 +114,11 @@ func (l *Ladon) DoPoliciesAllow(r *Request, policies []Policy) (err error) {
continue
}

// Is the policies effect deny? If yes, this overrides all allow policies -> access denied.
// Is the policy's effect `deny`? If yes, this overrides all allow policies -> access denied.
if !p.AllowAccess() {
deciders = append(deciders, p)
l.auditLogger().LogRejectedAccessRequest(r, policies, deciders)
go l.metric().RequestDeniedBy(*r, p)
return errors.WithStack(ErrRequestForcefullyDenied)
}

Expand All @@ -113,10 +127,14 @@ func (l *Ladon) DoPoliciesAllow(r *Request, policies []Policy) (err error) {
}

if !allowed {
go l.metric().RequestNoMatch(*r)

l.auditLogger().LogRejectedAccessRequest(r, policies, deciders)
return errors.WithStack(ErrRequestDenied)
}

l.metric().RequestAllowedBy(*r, deciders)

l.auditLogger().LogGrantedAccessRequest(r, policies, deciders)
return nil
}
Expand Down
33 changes: 33 additions & 0 deletions metric.go
@@ -0,0 +1,33 @@
/*
* Copyright © 2016-2018 Aeneas Rekkas <aeneas+oss@aeneas.io>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @author Aeneas Rekkas <aeneas+oss@aeneas.io>
* @copyright 2015-2018 Aeneas Rekkas <aeneas+oss@aeneas.io>
* @license Apache-2.0
*/

package ladon

// Metric is used to expose metrics about authz
type Metric interface {
// RequestDeniedBy is called when we get explicit deny by policy
RequestDeniedBy(Request, Policy)
// RequestAllowedBy is called when a matching policy has been found.
RequestAllowedBy(Request, Policies)
// RequestNoMatch is called when no policy has matched our request
RequestNoMatch(Request)
// RequestProcessingError is called when unexpected error occured
RequestProcessingError(Request, Policy, error)
}
31 changes: 31 additions & 0 deletions metric_noop.go
@@ -0,0 +1,31 @@
/*
* Copyright © 2016-2018 Aeneas Rekkas <aeneas+oss@aeneas.io>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @author Aeneas Rekkas <aeneas+oss@aeneas.io>
* @copyright 2015-2018 Aeneas Rekkas <aeneas+oss@aeneas.io>
* @license Apache-2.0
*/

package ladon

// MetricNoOp is the default metrics implementation , that tracks nothing.
type MetricNoOp struct{}

func (*MetricNoOp) RequestDeniedBy(r Request, p Policy) {}
func (*MetricNoOp) RequestAllowedBy(r Request, p Policies) {}
func (*MetricNoOp) RequestNoMatch(r Request) {}
func (*MetricNoOp) RequestProcessingError(r Request, p Policy, err error) {}

var DefaultMetric = &MetricNoOp{}

0 comments on commit 0e3588c

Please sign in to comment.