-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathacl.go
116 lines (97 loc) · 2.84 KB
/
acl.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
package acl
import (
"context"
"fmt"
"path"
"strings"
radix "github.com/edufschmidt/go-radix-tree"
)
const (
capabilityDeny = "deny"
)
// ACL is used to convert a set of policies into a structure that
// can be efficiently evaluated to determine if an action is allowed.
type ACL struct {
privileged bool
capabilities map[string]*radix.Tree
}
// CheckAuthorized verifies whether the ACL is authorized to perform a specific action.
// If the ACL is not authorized, an error is returned, which provides more details.
// If an operation is not explicitly enabled in the ACL, it is forbidden by default.
func (a *ACL) CheckAuthorized(ctx context.Context, res string, path string, op string) error {
// A privileged ACL is able to do anything.
if a.privileged {
return nil
}
capabilities, err := a.queryCapabilities(ctx, res, path)
if err != nil {
return err
}
if capabilities.HasCapability(op) {
return nil
}
return ErrUnauthorized
}
// queryCapabilities searches the ACL for all rules matching the queried resource
// instance, and merges the capabilities they enable into one single capabilityMap,
// which can then be used to easily verify whether a capability is set or not.
func (a *ACL) queryCapabilities(ctx context.Context, resource string, instance string) (capMap, error) {
capTree, ok := a.capabilities[resource]
if !ok {
return nil, ErrInvalidResource
}
if v, found := capTree.Get(instance); found {
return v.(capMap), nil
}
// Find all patterns in the capability tree
// matching the queried instance.
merged := make(capMap)
capTree.Walk(func(pattern string, raw interface{}) bool {
if matched, _ := path.Match(pattern, instance); matched {
for cap := range raw.(capMap) {
merged.AddCapability(cap)
}
return false
}
return true
})
return merged, nil
}
// String builds a string representation of an ACL which
// can be useful for debugging purposes.
func (a *ACL) String() string {
s := ""
s += fmt.Sprintf("- management = %v\n", a.privileged)
for res, capTree := range a.capabilities {
s += fmt.Sprintf("-- %s capabilities\n", res)
capTree.Walk(func(k string, v interface{}) bool {
s += fmt.Sprintf(" %s%s --> ", k, strings.Repeat(" ", 18-len(k)))
for c := range v.(capMap) {
s += fmt.Sprintf("%s ", c)
}
s += fmt.Sprintf("\n")
return false
})
}
return s
}
// capMap is a map meant for making it easy/efficient to
// evaluate whether or not a given capability is enabled. If an
// entry exists in the map for a specific capability, it is enabled.
// Otherwise, it is not.
type capMap map[string]struct{}
func (m capMap) AddCapability(cap string) {
m[cap] = struct{}{}
}
func (m capMap) RemoveCapability(cap string) {
delete(m, cap)
}
func (m capMap) HasCapability(cap string) bool {
_, found := m[cap]
return found
}
func (m capMap) Clear() {
for cap := range m {
delete(m, cap)
}
}