-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathstore.go
240 lines (197 loc) · 6.61 KB
/
store.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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
package store
import (
"context"
"fmt"
"github.com/go-logr/logr"
"github.com/weaveworks/weave-gitops-enterprise/pkg/query/internal/models"
"gorm.io/gorm"
"k8s.io/kubectl/pkg/util/slice"
)
//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 -generate
//counterfeiter:generate . Store
type Store interface {
StoreWriter
StoreReader
}
// StoreWriter is an interface for storing access rules and objects
//
//counterfeiter:generate . StoreWriter
type StoreWriter interface {
StoreRoles(ctx context.Context, roles []models.Role) error
StoreRoleBindings(ctx context.Context, roleBindings []models.RoleBinding) error
StoreObjects(ctx context.Context, objects []models.Object) error
StoreTenants(ctx context.Context, tenants []models.Tenant) error
DeleteObjects(ctx context.Context, object []models.Object) error
DeleteAllObjects(ctx context.Context, clusters []string) error
DeleteRoles(ctx context.Context, roles []models.Role) error
DeleteAllRoles(ctx context.Context, clusters []string) error
DeleteRoleBindings(ctx context.Context, roleBindings []models.RoleBinding) error
DeleteAllRoleBindings(ctx context.Context, clusters []string) error
DeleteTenants(ctx context.Context, tenants []models.Tenant) error
}
type QueryOperand string
const (
OperandEqual QueryOperand = "equal"
OperandNotEqual QueryOperand = "not_equal"
)
type GlobalOperand string
const (
GlobalOperandAnd GlobalOperand = "and"
GlobalOperandOr GlobalOperand = "or"
)
// Query describes how to filter the results of a search.
// The filters are applied, and then terms are (logically) ANDed together.
// Only results that match on both filters and terms are returned.
// https://blevesearch.com/docs/Query/
type Query interface {
GetTerms() string
GetFilters() []string
}
type QueryOption interface {
GetLimit() int32
GetOffset() int32
GetOrderBy() string
GetDescending() bool
}
// StoreReader is an interface for querying objects
//
//counterfeiter:generate . StoreReader
type StoreReader interface {
GetObjectByID(ctx context.Context, id string) (models.Object, error)
GetObjects(ctx context.Context, ids []string, opts QueryOption) (Iterator, error)
GetAllObjects(ctx context.Context) (Iterator, error)
GetRoles(ctx context.Context) ([]models.Role, error)
GetRoleBindings(ctx context.Context) ([]models.RoleBinding, error)
GetAccessRules(ctx context.Context) ([]models.AccessRule, error)
GetTenants(ctx context.Context) ([]models.Tenant, error)
}
// Iterator provides an iterable interface for requesting the next row of an object.
// Since we are doing the access filtering outside of the database, we need to
// ensure we are "filling" up the limit of the query.
//
//counterfeiter:generate . Iterator
type Iterator interface {
// Next returns true if there is another row to be read
Next() bool
// Row returns the next row of the iterator
Row() (models.Object, error)
// All returns all rows of the iterator
All() ([]models.Object, error)
// Page returns a specified number of rows of the iterator with a specified offset
Page(count int, offset int) ([]models.Object, error)
// Close closes the iterator
Close() error
}
type StorageBackend string
const (
StorageBackendSQLite StorageBackend = "sqlite"
)
// factory method that by default creates a in memory store
func NewStore(backend StorageBackend, uri string, log logr.Logger) (Store, error) {
switch backend {
case StorageBackendSQLite:
db, err := CreateSQLiteDB(uri)
if err != nil {
return nil, fmt.Errorf("error creating sqlite db: %w", err)
}
return NewSQLiteStore(db, log)
default:
return nil, fmt.Errorf("unknown storage backend: %s", backend)
}
}
var DefaultVerbsRequiredForAccess = []string{"list"}
// DeriveAcceessRules computes the access rules for a given set of roles and role bindings.
// This is implemented as a helper function to keep this logic testable and storage backend agnostic.
func DeriveAccessRules(roles []models.Role, bindings []models.RoleBinding) []models.AccessRule {
accessRules := []models.AccessRule{}
// Figure out the binding/role pairs
for _, role := range roles {
for _, binding := range bindings {
if bindingRoleMatch(binding, role) {
rule := convertToAccessRule(role.Cluster, role, binding, DefaultVerbsRequiredForAccess)
accessRules = append(accessRules, rule)
}
}
}
return accessRules
}
func bindingRoleMatch(binding models.RoleBinding, role models.Role) bool {
if binding.Cluster != role.Cluster {
return false
}
if binding.Namespace != role.Namespace {
return false
}
if binding.RoleRefKind != role.Kind {
return false
}
if binding.RoleRefName != role.Name {
return false
}
return true
}
func convertToAccessRule(clusterName string, role models.Role, binding models.RoleBinding, requiredVerbs []string) models.AccessRule {
rules := role.PolicyRules
derivedAccess := map[string]map[string]bool{}
accessibleResourceNames := []string{}
// {wego.weave.works: {Application: true, Source: true}}
for _, rule := range rules {
for _, apiGroup := range models.SplitRuleData(rule.APIGroups) {
if _, ok := derivedAccess[apiGroup]; !ok {
derivedAccess[apiGroup] = map[string]bool{}
}
rList := models.SplitRuleData(rule.Resources)
if models.ContainsWildcard(rList) {
derivedAccess[apiGroup]["*"] = true
}
vList := models.SplitRuleData(rule.Verbs)
if models.ContainsWildcard(vList) || hasVerbs(vList, requiredVerbs) {
for _, resource := range rList {
derivedAccess[apiGroup][resource] = true
}
}
}
for _, resource := range models.SplitRuleData(rule.ResourceNames) {
if resource != "" {
accessibleResourceNames = append(accessibleResourceNames, resource)
}
}
}
accessibleKinds := []string{}
for group, resources := range derivedAccess {
for k, v := range resources {
if v {
accessibleKinds = append(accessibleKinds, fmt.Sprintf("%s/%s", group, k))
}
}
}
return models.AccessRule{
Cluster: clusterName,
Namespace: role.Namespace,
AccessibleKinds: accessibleKinds,
Subjects: binding.Subjects,
ProvidedByRole: fmt.Sprintf("%s/%s", role.Kind, role.Name),
ProvidedByBinding: fmt.Sprintf("%s/%s", binding.Kind, binding.Name),
AccessibleResourceNames: accessibleResourceNames,
}
}
func hasVerbs(a, b []string) bool {
for _, v := range b {
if models.ContainsWildcard(a) {
return true
}
if slice.ContainsString(a, v, nil) {
return true
}
}
return false
}
func SeedObjects(db *gorm.DB, rows []models.Object) error {
withID := []models.Object{}
for _, o := range rows {
o.ID = o.GetID()
withID = append(withID, o)
}
result := db.Create(&withID)
return result.Error
}