Skip to content

Commit

Permalink
Introduces AuditLogger (#91)
Browse files Browse the repository at this point in the history
Closes #85
  • Loading branch information
leplatrem authored and arekkas committed Nov 13, 2017
1 parent 7285d8c commit 8128aec
Show file tree
Hide file tree
Showing 6 changed files with 176 additions and 2 deletions.
25 changes: 25 additions & 0 deletions README.md
Expand Up @@ -49,6 +49,9 @@ ORY builds solutions for better internet security and accessibility. We have a c
- [Adding Custom Conditions](#adding-custom-conditions)
- [Persistence](#persistence)
- [Access Control (Warden)](#access-control-warden)
- [Audit Log (Warden)](#audit-log-warden)
- [Limitations](#limitations)
- [Regular expressions](#regular-expressions)
- [Examples](#examples)
- [Good to know](#good-to-know)
- [Useful commands](#useful-commands)
Expand Down Expand Up @@ -570,6 +573,28 @@ func main() {
}
```

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

```go
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.
## Limitations
Ladon's limitations are listed here.
Expand Down
7 changes: 7 additions & 0 deletions audit_logger.go
@@ -0,0 +1,7 @@
package ladon

// AuditLogger tracks denied and granted authorizations.
type AuditLogger interface {
LogRejectedAccessRequest(request *Request, pool Policies, deciders Policies)
LogGrantedAccessRequest(request *Request, pool Policies, deciders Policies)
}
44 changes: 44 additions & 0 deletions audit_logger_info.go
@@ -0,0 +1,44 @@
package ladon

import (
"log"
"os"
"strings"
)

// AuditLoggerInfo outputs information about granting or rejecting policies.
type AuditLoggerInfo struct {
Logger *log.Logger
}

func (a *AuditLoggerInfo) logger() *log.Logger {
if a.Logger == nil {
a.Logger = log.New(os.Stderr, "", log.LstdFlags)
}
return a.Logger
}

func (a *AuditLoggerInfo) LogRejectedAccessRequest(r *Request, p Policies, d Policies) {
if len(d) > 1 {
allowed := joinPoliciesNames(d[0 : len(d)-1])
denied := d[len(d)-1].GetID()
a.Logger.Printf("policies %s allow access, but policy %s forcefully denied it", allowed, denied)
} else if len(d) == 1 {
denied := d[len(d)-1].GetID()
a.Logger.Printf("policy %s forcefully denied the access", denied)
} else {
a.Logger.Printf("no policy allowed access")
}
}

func (a *AuditLoggerInfo) LogGrantedAccessRequest(r *Request, p Policies, d Policies) {
a.Logger.Printf("policies %s allow access", joinPoliciesNames(d))
}

func joinPoliciesNames(policies Policies) string {
names := []string{}
for _, policy := range policies {
names = append(names, policy.GetID())
}
return strings.Join(names, ", ")
}
9 changes: 9 additions & 0 deletions audit_logger_noop.go
@@ -0,0 +1,9 @@
package ladon

// AuditLoggerNoOp is the default AuditLogger, that tracks nothing.
type AuditLoggerNoOp struct{}

func (*AuditLoggerNoOp) LogRejectedAccessRequest(r *Request, p Policies, d Policies) {}
func (*AuditLoggerNoOp) LogGrantedAccessRequest(r *Request, p Policies, d Policies) {}

var DefaultAuditLogger = &AuditLoggerNoOp{}
74 changes: 74 additions & 0 deletions audit_logger_test.go
@@ -0,0 +1,74 @@
package ladon_test

import (
"bytes"
"log"
"testing"

. "github.com/ory/ladon"
. "github.com/ory/ladon/manager/memory"
"github.com/stretchr/testify/assert"
)

func TestAuditLogger(t *testing.T) {
var output bytes.Buffer

warden := &Ladon{
Manager: NewMemoryManager(),
AuditLogger: &AuditLoggerInfo{
Logger: log.New(&output, "", 0),
},
}

warden.Manager.Create(&DefaultPolicy{
ID: "no-updates",
Subjects: []string{"<.*>"},
Actions: []string{"update"},
Resources: []string{"<.*>"},
Effect: DenyAccess,
})
warden.Manager.Create(&DefaultPolicy{
ID: "yes-deletes",
Subjects: []string{"<.*>"},
Actions: []string{"delete"},
Resources: []string{"<.*>"},
Effect: AllowAccess,
})
warden.Manager.Create(&DefaultPolicy{
ID: "no-bob",
Subjects: []string{"bob"},
Actions: []string{"delete"},
Resources: []string{"<.*>"},
Effect: DenyAccess,
})

r := &Request{}
assert.NotNil(t, warden.IsAllowed(r))
assert.Equal(t, "no policy allowed access\n", output.String())

output.Reset()

r = &Request{
Action: "update",
}
assert.NotNil(t, warden.IsAllowed(r))
assert.Equal(t, "policy no-updates forcefully denied the access\n", output.String())

output.Reset()

r = &Request{
Subject: "bob",
Action: "delete",
}
assert.NotNil(t, warden.IsAllowed(r))
assert.Equal(t, "policies yes-deletes allow access, but policy no-bob forcefully denied it\n", output.String())

output.Reset()

r = &Request{
Subject: "alice",
Action: "delete",
}
assert.Nil(t, warden.IsAllowed(r))
assert.Equal(t, "policies yes-deletes allow access\n", output.String())
}
19 changes: 17 additions & 2 deletions ladon.go
Expand Up @@ -20,8 +20,9 @@ import (

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

func (l *Ladon) matcher() matcher {
Expand All @@ -31,6 +32,13 @@ func (l *Ladon) matcher() matcher {
return l.Matcher
}

func (l *Ladon) auditLogger() AuditLogger {
if l.AuditLogger == nil {
l.AuditLogger = DefaultAuditLogger
}
return l.AuditLogger
}

// 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)
Expand All @@ -48,6 +56,7 @@ func (l *Ladon) IsAllowed(r *Request) (err error) {
// 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 {
Expand Down Expand Up @@ -88,15 +97,21 @@ func (l *Ladon) DoPoliciesAllow(r *Request, policies []Policy) (err error) {

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

allowed = true
deciders = append(deciders, p)
}

if !allowed {
l.auditLogger().LogRejectedAccessRequest(r, policies, deciders)
return errors.WithStack(ErrRequestDenied)
}

l.auditLogger().LogGrantedAccessRequest(r, policies, deciders)
return nil
}

Expand Down

0 comments on commit 8128aec

Please sign in to comment.