forked from cilium/cilium
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathrepository.go
414 lines (345 loc) · 11.2 KB
/
repository.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
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
// Copyright 2016-2017 Authors of Cilium
//
// 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.
package policy
import (
"encoding/json"
"github.com/cilium/cilium/pkg/labels"
"github.com/cilium/cilium/pkg/lock"
"github.com/cilium/cilium/pkg/policy/api"
log "github.com/sirupsen/logrus"
)
// Repository is a list of policy rules which in combination form the security
// policy. A policy repository can be
type Repository struct {
// Mutex protects the whole policy tree
Mutex lock.RWMutex
rules []*rule
// revision is the revision of the policy repository. It will be
// incremented whenever the policy repository is changed
revision uint64
}
// NewPolicyRepository allocates a new policy repository
func NewPolicyRepository() *Repository {
return &Repository{}
}
// traceState is an internal structure used to collect information
// while determining policy decision
type traceState struct {
// selectedRules is the number of rules with matching EndpointSelector
selectedRules int
// matchedRules is the number of rules that have allowed traffic
matchedRules int
// constrainedRules counts how many "FromRequires" constraints are
// unsatisfied
constrainedRules int
// ruleID is the rule ID currently being evaluated
ruleID int
}
func (state *traceState) trace(p *Repository, ctx *SearchContext) {
ctx.PolicyTrace("%d/%d rules selected\n", state.selectedRules, len(p.rules))
if state.constrainedRules > 0 {
ctx.PolicyTrace("Found unsatisfied FromRequires constraint\n")
} else if state.matchedRules > 0 {
ctx.PolicyTrace("Found allow rule\n")
} else {
ctx.PolicyTrace("Found no allow rule\n")
}
}
// CanReachRLocked evaluates the policy repository for the provided search
// context and returns the verdict or api.Undecided if no rule matches. The
// policy repository mutex must be held.
func (p *Repository) CanReachRLocked(ctx *SearchContext) api.Decision {
decision := api.Undecided
state := traceState{}
loop:
for i, r := range p.rules {
state.ruleID = i
switch r.canReach(ctx, &state) {
// The rule contained a constraint which was not met, this
// connection is not allowed
case api.Denied:
decision = api.Denied
break loop
// The rule allowed the connection but a later rule may impose
// additional constraints, so we store the decision but allow
// it to be overwritten by an additional requirement
case api.Allowed:
decision = api.Allowed
}
}
state.trace(p, ctx)
return decision
}
// AllowsLabelAccess evaluates the policy repository for the provided search
// context and returns the verdict. If no matching policy allows for the
// connection, the request will be denied. The policy repository mutex must be
// held.
func (p *Repository) AllowsLabelAccess(ctx *SearchContext) api.Decision {
ctx.PolicyTrace("Tracing %s\n", ctx.String())
decision := api.Denied
if len(p.rules) == 0 {
ctx.PolicyTrace(" No rules found\n")
} else {
if p.CanReachRLocked(ctx) == api.Allowed {
decision = api.Allowed
}
}
ctx.PolicyTrace("Label verdict: %s", decision.String())
return decision
}
// ResolveL4Policy resolves the L4 policy for a set of endpoints by searching
// the policy repository for `PortRule` rules that are attached to a `Rule`
// where the EndpointSelector matches `ctx.To`. `ctx.From` takes no effect and
// is ignored in the search. If multiple `PortRule` rules are found, all rules
// are merged together. If rules contains overlapping port definitions, the first
// rule found in the repository takes precedence.
//
// TODO: Coalesce l7 rules?
func (p *Repository) ResolveL4Policy(ctx *SearchContext) (*L4Policy, error) {
result := NewL4Policy()
ctx.PolicyTrace("\n")
if ctx.EgressL4Only {
ctx.PolicyTrace("Resolving egress port policy for %+v\n", ctx.To)
} else if ctx.IngressL4Only {
ctx.PolicyTrace("Resolving ingress port policy for %+v\n", ctx.To)
} else {
ctx.PolicyTrace("Resolving port policy for %+v\n", ctx.To)
}
state := traceState{}
for _, r := range p.rules {
found, err := r.resolveL4Policy(ctx, &state, result)
if err != nil {
return nil, err
}
state.ruleID++
if found != nil {
state.matchedRules++
}
}
state.trace(p, ctx)
return result, nil
}
// ResolveL3Policy resolves the L3 policy for a set of endpoints by searching
// the policy repository for `CIDR` rules that are attached to a `Rule`
// where the EndpointSelector matches `ctx.To`. `ctx.From` takes no effect and
// is ignored in the search.
func (p *Repository) ResolveL3Policy(ctx *SearchContext) *L3Policy {
result := NewL3Policy()
ctx.PolicyTrace("Resolving L3 (CIDR) policy for %+v\n", ctx.To)
state := traceState{}
for _, r := range p.rules {
r.resolveL3Policy(ctx, &state, result)
state.ruleID++
}
state.trace(p, ctx)
return result
}
func (p *Repository) allowsL4Egress(searchCtx *SearchContext) api.Decision {
ctx := *searchCtx
ctx.To = ctx.From
ctx.From = labels.LabelArray{}
ctx.EgressL4Only = true
policy, err := p.ResolveL4Policy(&ctx)
if err != nil {
log.WithError(err).Warn("Evaluation error while resolving L4 egress policy")
}
verdict := api.Undecided
if err == nil && len(policy.Egress) > 0 {
verdict = policy.EgressCoversDPorts(ctx.DPorts)
}
if len(ctx.DPorts) == 0 {
ctx.PolicyTrace("L4 egress verdict: [no port context specified]")
} else {
ctx.PolicyTrace("L4 egress verdict: %s", verdict.String())
}
return verdict
}
func (p *Repository) allowsL4Ingress(ctx *SearchContext) api.Decision {
ctx.IngressL4Only = true
policy, err := p.ResolveL4Policy(ctx)
if err != nil {
log.WithError(err).Warn("Evaluation error while resolving L4 ingress policy")
}
verdict := api.Undecided
if err == nil && len(policy.Ingress) > 0 {
verdict = policy.IngressCoversContext(ctx)
}
if len(ctx.DPorts) == 0 {
ctx.PolicyTrace("L4 ingress verdict: [no port context specified]")
} else {
ctx.PolicyTrace("L4 ingress verdict: %s", verdict.String())
}
return verdict
}
// AllowsRLocked evaluates the policy repository for the provided search
// context and returns the verdict. If no matching policy allows for the
// connection, the request will be denied. The policy repository mutex must be
// held.
func (p *Repository) AllowsRLocked(ctx *SearchContext) api.Decision {
ctx.PolicyTrace("Tracing %s\n", ctx.String())
decision := p.CanReachRLocked(ctx)
ctx.PolicyTrace("Label verdict: %s", decision.String())
if decision == api.Allowed {
ctx.PolicyTrace("L4 ingress & egress policies skipped")
return decision
}
// We only report the overall decision as L4 inclusive if a port has
// been specified
if len(ctx.DPorts) != 0 {
l4Egress := p.allowsL4Egress(ctx)
l4Ingress := p.allowsL4Ingress(ctx)
// Explicit deny should deny; Allow+Undecided should allow
if l4Egress == api.Denied || l4Ingress == api.Denied {
decision = api.Denied
} else if l4Egress == api.Allowed || l4Ingress == api.Allowed {
decision = api.Allowed
}
}
if decision != api.Allowed {
decision = api.Denied
}
return decision
}
// SearchRLocked searches the policy repository for rules which match the
// specified labels and will return an array of all rules which matched.
func (p *Repository) SearchRLocked(labels labels.LabelArray) api.Rules {
result := api.Rules{}
for _, r := range p.rules {
if r.Labels.Contains(labels) {
result = append(result, &r.Rule)
}
}
return result
}
// Add inserts a rule into the policy repository
func (p *Repository) Add(r api.Rule) (uint64, error) {
p.Mutex.Lock()
defer p.Mutex.Unlock()
realRule := &rule{Rule: r}
if err := realRule.sanitize(); err != nil {
return p.revision, err
}
p.rules = append(p.rules, realRule)
p.revision++
return p.revision, nil
}
// AddListLocked inserts a rule into the policy repository with the repository already locked
func (p *Repository) AddListLocked(rules api.Rules) (uint64, error) {
// Validate entire rule list first and only append array if
// all rules are valid
newList := make([]*rule, len(rules))
for i := range rules {
newList[i] = &rule{Rule: *rules[i]}
if err := newList[i].sanitize(); err != nil {
return p.revision, err
}
}
p.rules = append(p.rules, newList...)
p.revision++
return p.revision, nil
}
// AddList inserts a rule into the policy repository
func (p *Repository) AddList(rules api.Rules) (uint64, error) {
p.Mutex.Lock()
defer p.Mutex.Unlock()
return p.AddListLocked(rules)
}
// DeleteByLabelsLocked deletes all rules in the policy repository which
// contain the specified labels
func (p *Repository) DeleteByLabelsLocked(labels labels.LabelArray) (uint64, int) {
deleted := 0
new := p.rules[:0]
for _, r := range p.rules {
if !r.Labels.Contains(labels) {
new = append(new, r)
} else {
deleted++
}
}
if deleted > 0 {
p.revision++
p.rules = new
}
return p.revision, deleted
}
// DeleteByLabels deletes all rules in the policy repository which contain the
// specified labels
func (p *Repository) DeleteByLabels(labels labels.LabelArray) (uint64, int) {
p.Mutex.Lock()
defer p.Mutex.Unlock()
return p.DeleteByLabelsLocked(labels)
}
// JSONMarshalRules returns a slice of policy rules as string in JSON
// representation
func JSONMarshalRules(rules api.Rules) string {
b, err := json.MarshalIndent(rules, "", " ")
if err != nil {
return err.Error()
}
return string(b)
}
// GetJSON returns all rules of the policy repository as string in JSON
// representation
func (p *Repository) GetJSON() string {
p.Mutex.RLock()
defer p.Mutex.RUnlock()
result := api.Rules{}
for _, r := range p.rules {
result = append(result, &r.Rule)
}
return JSONMarshalRules(result)
}
// GetRulesMatching returns whether any of the rules in a repository contain a
// rule with labels matching the labels in the provided LabelArray.
//
// Must be called with p.Mutex held
func (p *Repository) GetRulesMatching(labels labels.LabelArray, includeEntities bool) bool {
for _, r := range p.rules {
rulesMatch := r.EndpointSelector.Matches(labels)
if rulesMatch {
return true
}
if includeEntities && len(r.toEntities)+len(r.fromEntities) > 0 {
return true
}
}
return false
}
// NumRules returns the amount of rules in the policy repository.
//
// Must be called with p.Mutex held
func (p *Repository) NumRules() int {
return len(p.rules)
}
// GetRevision returns the revision of the policy repository
func (p *Repository) GetRevision() uint64 {
return p.revision
}
// TranslateRules traverses rules and applies provided translator to rules
func (p *Repository) TranslateRules(translator Translator) error {
p.Mutex.Lock()
defer p.Mutex.Unlock()
for ruleIndex := range p.rules {
if err := translator.Translate(&p.rules[ruleIndex].Rule); err != nil {
return err
}
}
return nil
}
// BumpRevision allows forcing policy regeneration
func (p *Repository) BumpRevision() {
p.Mutex.Lock()
defer p.Mutex.Unlock()
p.revision++
}