-
Notifications
You must be signed in to change notification settings - Fork 568
/
authorize_req.go
142 lines (120 loc) · 4.17 KB
/
authorize_req.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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
package server
import (
"context"
"sort"
"github.com/pachyderm/pachyderm/v2/src/internal/transactionenv/txncontext"
"github.com/pachyderm/pachyderm/v2/src/auth"
)
type groupLookupFn func(ctx context.Context, txnCtx *txncontext.TransactionContext, subject string) ([]string, error)
// authorizeRequest is a helper struct used to evaluate an incoming Authorize request.
// It's initialized with the subject and set of permissions required for an Operation,
// and it looks at role bindings to figure out which permissions are satisfied.
// This decouples evaluating role bindings from fetching the role bindings,
// so we can easily try the cheapest/most likely options and exit early if
// the permissions are all satisfied.
type authorizeRequest struct {
subject string
permissions map[auth.Permission]bool
roleMap map[string]*auth.Role
satisfiedPermissions []auth.Permission
groupsForSubject groupLookupFn
groups []string
}
func newAuthorizeRequest(subject string, permissions map[auth.Permission]bool, groupsForSubject groupLookupFn) *authorizeRequest {
return &authorizeRequest{
subject: subject,
roleMap: make(map[string]*auth.Role),
permissions: permissions,
groupsForSubject: groupsForSubject,
satisfiedPermissions: make([]auth.Permission, 0),
}
}
func (r *authorizeRequest) rolesForResourceType(rt auth.ResourceType) []string {
roles := make([]string, 0, len(r.roleMap))
for r, def := range r.roleMap {
if roleReturnedForResource(def, rt) {
roles = append(roles, r)
}
}
sort.Strings(roles)
return roles
}
func (r *authorizeRequest) satisfiedForResourceType(rt auth.ResourceType) []auth.Permission {
roles := r.rolesForResourceType(rt)
perms := make([]auth.Permission, 0)
for _, role := range roles {
perms = append(perms, r.roleMap[role].Permissions...)
}
return perms
}
// isSatisfied returns true if no permissions remain
func (r *authorizeRequest) isSatisfied() bool {
return len(r.permissions) == 0
}
func (r *authorizeRequest) missing() []auth.Permission {
missing := make([]auth.Permission, 0, len(r.permissions))
for p := range r.permissions {
missing = append(missing, p)
}
return missing
}
// evaluateRoleBinding removes permissions that are satisfied by the role binding from the
// set of desired permissions. A subject derives permissions from:
// - role bindings that refer to them by name
// - role bindings that refer to allClusterUsers
// - role bindings that refer to any group the subject belongs to
func (r *authorizeRequest) evaluateRoleBinding(ctx context.Context, txnCtx *txncontext.TransactionContext, binding *auth.RoleBinding) error {
if err := r.evaluateRoleBindingForSubject(r.subject, binding); err != nil {
return err
}
if len(r.permissions) == 0 {
return nil
}
if err := r.evaluateRoleBindingForSubject(auth.AllClusterUsersSubject, binding); err != nil {
return err
}
// If all permissions are satisfied without checking group membership, then return early
if len(r.permissions) == 0 {
return nil
}
// Cache the group membership after the first lookup, in case we need to evaluate several
// bindings to cover the set of permissions.
if r.groups == nil {
var err error
r.groups, err = r.groupsForSubject(ctx, txnCtx, r.subject)
if err != nil {
return err
}
}
for _, g := range r.groups {
if err := r.evaluateRoleBindingForSubject(g, binding); err != nil {
return err
}
}
return nil
}
func (r *authorizeRequest) evaluateRoleBindingForSubject(subject string, binding *auth.RoleBinding) error {
if binding.Entries == nil {
return nil
}
if entry, ok := binding.Entries[subject]; ok {
for role := range entry.Roles {
// Don't look up permissions for a role we already saw in another binding
if _, ok := r.roleMap[role]; ok {
continue
}
roleDefinition, err := getRole(role)
if err != nil {
return err
}
r.roleMap[role] = roleDefinition.role
for _, permission := range roleDefinition.role.Permissions {
if _, ok := r.permissions[permission]; ok {
r.satisfiedPermissions = append(r.satisfiedPermissions, permission)
delete(r.permissions, permission)
}
}
}
}
return nil
}