istio

Форк
0
574 строки · 18.7 Кб
1
// Copyright Istio Authors
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
//     http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14

15
package ambient
16

17
import (
18
	"fmt"
19
	"net/netip"
20
	"strconv"
21
	"strings"
22

23
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
24

25
	"istio.io/api/security/v1beta1"
26
	securityclient "istio.io/client-go/pkg/apis/security/v1beta1"
27
	"istio.io/istio/pilot/pkg/model"
28
	"istio.io/istio/pkg/config/schema/gvk"
29
	"istio.io/istio/pkg/config/schema/kind"
30
	"istio.io/istio/pkg/log"
31
	"istio.io/istio/pkg/util/sets"
32
	"istio.io/istio/pkg/workloadapi/security"
33
)
34

35
const (
36
	staticStrictPolicyName = "istio_converted_static_strict" // use '_' character since those are illegal in k8s names
37
)
38

39
func (a *index) Policies(requested sets.Set[model.ConfigKey]) []model.WorkloadAuthorization {
40
	// TODO: use many Gets instead of List?
41
	cfgs := a.authorizationPolicies.List(metav1.NamespaceAll)
42
	l := len(cfgs)
43
	if len(requested) > 0 {
44
		l = len(requested)
45
	}
46
	res := make([]model.WorkloadAuthorization, 0, l)
47
	for _, cfg := range cfgs {
48
		k := model.ConfigKey{
49
			Kind:      kind.AuthorizationPolicy,
50
			Name:      cfg.Authorization.Name,
51
			Namespace: cfg.Authorization.Namespace,
52
		}
53

54
		if len(requested) > 0 && !requested.Contains(k) {
55
			continue
56
		}
57
		res = append(res, cfg)
58
	}
59
	return res
60
}
61

62
// convertedSelectorPeerAuthentications returns a list of keys corresponding to one or both of:
63
// [static STRICT policy, port-level STRICT policy] based on the effective PeerAuthentication policy
64
func convertedSelectorPeerAuthentications(rootNamespace string, configs []*securityclient.PeerAuthentication) []string {
65
	var meshCfg, namespaceCfg, workloadCfg *securityclient.PeerAuthentication
66
	for _, cfg := range configs {
67
		spec := &cfg.Spec
68
		if spec.Selector == nil || len(spec.Selector.MatchLabels) == 0 {
69
			// Namespace-level or mesh-level policy
70
			if cfg.Namespace == rootNamespace {
71
				if meshCfg == nil || cfg.CreationTimestamp.Before(&meshCfg.CreationTimestamp) {
72
					log.Debugf("Switch selected mesh policy to %s.%s (%v)", cfg.Name, cfg.Namespace, cfg.CreationTimestamp)
73
					meshCfg = cfg
74
				}
75
			} else {
76
				if namespaceCfg == nil || cfg.CreationTimestamp.Before(&namespaceCfg.CreationTimestamp) {
77
					log.Debugf("Switch selected namespace policy to %s.%s (%v)", cfg.Name, cfg.Namespace, cfg.CreationTimestamp)
78
					namespaceCfg = cfg
79
				}
80
			}
81
		} else if cfg.Namespace != rootNamespace {
82
			if workloadCfg == nil || cfg.CreationTimestamp.Before(&workloadCfg.CreationTimestamp) {
83
				log.Debugf("Switch selected workload policy to %s.%s (%v)", cfg.Name, cfg.Namespace, cfg.CreationTimestamp)
84
				workloadCfg = cfg
85
			}
86
		}
87
	}
88

89
	// Whether it comes from a mesh-wide, namespace-wide, or workload-specific policy
90
	// if the effective policy is STRICT, then reference our static STRICT policy
91
	var isEffectiveStrictPolicy bool
92
	// Only 1 per port workload policy can be effective at a time. In the case of a conflict
93
	// the oldest policy wins.
94
	var effectivePortLevelPolicyKey string
95

96
	// Process in mesh, namespace, workload order to resolve inheritance (UNSET)
97
	if meshCfg != nil {
98
		if !isMtlsModeUnset(meshCfg.Spec.Mtls) {
99
			isEffectiveStrictPolicy = isMtlsModeStrict(meshCfg.Spec.Mtls)
100
		}
101
	}
102

103
	if namespaceCfg != nil {
104
		if !isMtlsModeUnset(namespaceCfg.Spec.Mtls) {
105
			isEffectiveStrictPolicy = isMtlsModeStrict(namespaceCfg.Spec.Mtls)
106
		}
107
	}
108

109
	if workloadCfg == nil {
110
		return effectivePeerAuthenticationKeys(rootNamespace, isEffectiveStrictPolicy, "")
111
	}
112

113
	workloadSpec := &workloadCfg.Spec
114

115
	// Regardless of if we have port-level overrides, if the workload policy is STRICT, then we need to reference our static STRICT policy
116
	if isMtlsModeStrict(workloadSpec.Mtls) {
117
		isEffectiveStrictPolicy = true
118
	}
119

120
	// Regardless of if we have port-level overrides, if the workload policy is PERMISSIVE or DISABLE, then we shouldn't send our static STRICT policy
121
	if isMtlsModePermissive(workloadSpec.Mtls) || isMtlsModeDisable(workloadSpec.Mtls) {
122
		isEffectiveStrictPolicy = false
123
	}
124

125
	if workloadSpec.PortLevelMtls != nil {
126
		switch workloadSpec.GetMtls().GetMode() {
127
		case v1beta1.PeerAuthentication_MutualTLS_STRICT:
128
			foundPermissive := false
129
			for _, portMtls := range workloadSpec.PortLevelMtls {
130
				if isMtlsModePermissive(portMtls) || isMtlsModeDisable(portMtls) {
131
					foundPermissive = true
132
					break
133
				}
134
			}
135

136
			if foundPermissive {
137
				// If we found a non-strict policy, we need to reference this workload policy to see the port level exceptions
138
				effectivePortLevelPolicyKey = workloadCfg.Namespace + "/" + model.GetAmbientPolicyConfigName(model.ConfigKey{
139
					Name:      workloadCfg.Name,
140
					Kind:      kind.PeerAuthentication,
141
					Namespace: workloadCfg.Namespace,
142
				})
143
				isEffectiveStrictPolicy = false // don't send our static STRICT policy since the converted form of this policy will include the default STRICT mode
144
			}
145
		case v1beta1.PeerAuthentication_MutualTLS_PERMISSIVE, v1beta1.PeerAuthentication_MutualTLS_DISABLE:
146
			foundStrict := false
147
			for _, portMtls := range workloadSpec.PortLevelMtls {
148
				if isMtlsModeStrict(portMtls) {
149
					foundStrict = true
150
					break
151
				}
152
			}
153

154
			// There's a STRICT port mode, so we need to reference this policy in the workload
155
			if foundStrict {
156
				effectivePortLevelPolicyKey = workloadCfg.Namespace + "/" + model.GetAmbientPolicyConfigName(model.ConfigKey{
157
					Name:      workloadCfg.Name,
158
					Kind:      kind.PeerAuthentication,
159
					Namespace: workloadCfg.Namespace,
160
				})
161
			}
162
		default: // Unset
163
			if isEffectiveStrictPolicy {
164
				// Strict mesh or namespace policy
165
				foundPermissive := false
166
				for _, portMtls := range workloadSpec.PortLevelMtls {
167
					if isMtlsModePermissive(portMtls) {
168
						foundPermissive = true
169
						break
170
					}
171
				}
172

173
				if foundPermissive {
174
					// If we found a non-strict policy, we need to reference this workload policy to see the port level exceptions
175
					effectivePortLevelPolicyKey = workloadCfg.Namespace + "/" + model.GetAmbientPolicyConfigName(model.ConfigKey{
176
						Name:      workloadCfg.Name,
177
						Kind:      kind.PeerAuthentication,
178
						Namespace: workloadCfg.Namespace,
179
					})
180
				}
181
			} else {
182
				// Permissive mesh or namespace policy
183
				isEffectiveStrictPolicy = false // any ports that aren't specified will be PERMISSIVE so this workload isn't effectively under a STRICT policy
184
				foundStrict := false
185
				for _, portMtls := range workloadSpec.PortLevelMtls {
186
					if isMtlsModeStrict(portMtls) {
187
						foundStrict = true
188
						continue
189
					}
190
				}
191

192
				// There's a STRICT port mode, so we need to reference this policy in the workload
193
				if foundStrict {
194
					effectivePortLevelPolicyKey = workloadCfg.Namespace + "/" + model.GetAmbientPolicyConfigName(model.ConfigKey{
195
						Name:      workloadCfg.Name,
196
						Kind:      kind.PeerAuthentication,
197
						Namespace: workloadCfg.Namespace,
198
					})
199
				}
200
			}
201
		}
202
	}
203

204
	return effectivePeerAuthenticationKeys(rootNamespace, isEffectiveStrictPolicy, effectivePortLevelPolicyKey)
205
}
206

207
func effectivePeerAuthenticationKeys(rootNamespace string, isEffectiveStringPolicy bool, effectiveWorkloadPolicyKey string) []string {
208
	res := sets.New[string]()
209

210
	if isEffectiveStringPolicy {
211
		res.Insert(fmt.Sprintf("%s/%s", rootNamespace, staticStrictPolicyName))
212
	}
213

214
	if effectiveWorkloadPolicyKey != "" {
215
		res.Insert(effectiveWorkloadPolicyKey)
216
	}
217

218
	return sets.SortedList(res)
219
}
220

221
// convertPeerAuthentication converts a PeerAuthentication to an L4 authorization policy (i.e. security.Authorization) iff
222
// 1. the PeerAuthentication has a workload selector
223
// 2. The PeerAuthentication is NOT in the root namespace
224
// 3. There is a portLevelMtls policy (technically implied by 1)
225
// 4. If the top-level mode is PERMISSIVE or DISABLE, there is at least one portLevelMtls policy with mode STRICT
226
//
227
// STRICT policies that don't have portLevelMtls will be
228
// handled when the Workload xDS resource is pushed (a static STRICT-equivalent policy will always be pushed)
229
func convertPeerAuthentication(rootNamespace string, cfg *securityclient.PeerAuthentication) *security.Authorization {
230
	pa := &cfg.Spec
231

232
	mode := pa.GetMtls().GetMode()
233

234
	scope := security.Scope_WORKLOAD_SELECTOR
235
	// violates case #1, #2, or #3
236
	if cfg.Namespace == rootNamespace || pa.Selector == nil || len(pa.PortLevelMtls) == 0 {
237
		log.Debugf("skipping PeerAuthentication %s/%s for ambient since it isn't a workload policy with port level mTLS", cfg.Namespace, cfg.Name)
238
		return nil
239
	}
240

241
	action := security.Action_DENY
242
	var rules []*security.Rules
243

244
	if mode == v1beta1.PeerAuthentication_MutualTLS_STRICT {
245
		rules = append(rules, &security.Rules{
246
			Matches: []*security.Match{
247
				{
248
					NotPrincipals: []*security.StringMatch{
249
						{
250
							MatchType: &security.StringMatch_Presence{},
251
						},
252
					},
253
				},
254
			},
255
		})
256
	}
257

258
	// If we have a strict policy and all of the ports are strict, it's effectively a strict policy
259
	// so we can exit early and have the WorkloadRbac xDS server push its static strict policy.
260
	// Note that this doesn't actually attach the policy to any workload; it just makes it available
261
	// to ztunnel in case a workload needs it.
262
	foundNonStrictPortmTLS := false
263
	for port, mtls := range pa.PortLevelMtls {
264
		switch portMtlsMode := mtls.GetMode(); {
265
		case portMtlsMode == v1beta1.PeerAuthentication_MutualTLS_STRICT:
266
			rules = append(rules, &security.Rules{
267
				Matches: []*security.Match{
268
					{
269
						NotPrincipals: []*security.StringMatch{
270
							{
271
								MatchType: &security.StringMatch_Presence{},
272
							},
273
						},
274
						DestinationPorts: []uint32{port},
275
					},
276
				},
277
			})
278
		case portMtlsMode == v1beta1.PeerAuthentication_MutualTLS_PERMISSIVE:
279
			// Check top-level mode
280
			if mode == v1beta1.PeerAuthentication_MutualTLS_PERMISSIVE || mode == v1beta1.PeerAuthentication_MutualTLS_DISABLE {
281
				// we don't care; log and continue
282
				log.Debugf("skipping port %s/%s for PeerAuthentication %s/%s for ambient since the parent mTLS mode is %s",
283
					port, portMtlsMode, cfg.Namespace, cfg.Name, mode)
284
				continue
285
			}
286
			foundNonStrictPortmTLS = true
287

288
			// If the top level policy is STRICT, we need to add a rule for the port that exempts it from the deny policy
289
			rules = append(rules, &security.Rules{
290
				Matches: []*security.Match{
291
					{
292
						NotDestinationPorts: []uint32{port}, // if the incoming connection does not match this port, deny (notice there's no principals requirement)
293
					},
294
				},
295
			})
296
		case portMtlsMode == v1beta1.PeerAuthentication_MutualTLS_DISABLE:
297
			// Check top-level mode
298
			if mode == v1beta1.PeerAuthentication_MutualTLS_PERMISSIVE || mode == v1beta1.PeerAuthentication_MutualTLS_DISABLE {
299
				// we don't care; log and continue
300
				log.Debugf("skipping port %s/%s for PeerAuthentication %s/%s for ambient since the parent mTLS mode is %s",
301
					port, portMtlsMode, cfg.Namespace, cfg.Name, mode)
302
				continue
303
			}
304
			foundNonStrictPortmTLS = true
305

306
			// If the top level policy is STRICT, we need to add a rule for the port that exempts it from the deny policy
307
			rules = append(rules, &security.Rules{
308
				Matches: []*security.Match{
309
					{
310
						NotDestinationPorts: []uint32{port}, // if the incoming connection does not match this port, deny (notice there's no principals requirement)
311
					},
312
				},
313
			})
314
		default:
315
			log.Debugf("skipping port %s for PeerAuthentication %s/%s for ambient since it is %s", port, cfg.Namespace, cfg.Name, portMtlsMode)
316
			continue
317
		}
318
	}
319

320
	// If the top level TLS mode is STRICT and all of the port level mTLS modes are STRICT, this is just a strict policy and we'll exit early
321
	if mode == v1beta1.PeerAuthentication_MutualTLS_STRICT && !foundNonStrictPortmTLS {
322
		return nil
323
	}
324

325
	if len(rules) == 0 {
326
		// we never added any rules; return
327
		return nil
328
	}
329

330
	opol := &security.Authorization{
331
		Name: model.GetAmbientPolicyConfigName(model.ConfigKey{
332
			Name:      cfg.Name,
333
			Kind:      kind.PeerAuthentication,
334
			Namespace: cfg.Namespace,
335
		}),
336
		Namespace: cfg.Namespace,
337
		Scope:     scope,
338
		Action:    action,
339
		Groups:    []*security.Group{{Rules: rules}},
340
	}
341

342
	return opol
343
}
344

345
func convertAuthorizationPolicy(rootns string, obj *securityclient.AuthorizationPolicy) *security.Authorization {
346
	pol := &obj.Spec
347

348
	polTargetRef := pol.GetTargetRef()
349
	if polTargetRef != nil &&
350
		polTargetRef.Group == gvk.KubernetesGateway.Group &&
351
		polTargetRef.Kind == gvk.KubernetesGateway.Kind {
352
		// we have a policy targeting a gateway, do not configure a WDS authorization
353
		return nil
354
	}
355

356
	scope := security.Scope_WORKLOAD_SELECTOR
357
	if pol.GetSelector() == nil {
358
		scope = security.Scope_NAMESPACE
359
		// TODO: TDA
360
		if rootns == obj.Namespace {
361
			scope = security.Scope_GLOBAL // TODO: global workload?
362
		}
363
	}
364
	action := security.Action_ALLOW
365
	switch pol.Action {
366
	case v1beta1.AuthorizationPolicy_ALLOW:
367
	case v1beta1.AuthorizationPolicy_DENY:
368
		action = security.Action_DENY
369
	default:
370
		return nil
371
	}
372
	opol := &security.Authorization{
373
		Name:      obj.Name,
374
		Namespace: obj.Namespace,
375
		Scope:     scope,
376
		Action:    action,
377
		Groups:    nil,
378
	}
379

380
	for _, rule := range pol.Rules {
381
		rules := handleRule(action, rule)
382
		if rules != nil {
383
			rg := &security.Group{
384
				Rules: rules,
385
			}
386
			opol.Groups = append(opol.Groups, rg)
387
		}
388
	}
389

390
	return opol
391
}
392

393
func anyNonEmpty[T any](arr ...[]T) bool {
394
	for _, a := range arr {
395
		if len(a) > 0 {
396
			return true
397
		}
398
	}
399
	return false
400
}
401

402
func handleRule(action security.Action, rule *v1beta1.Rule) []*security.Rules {
403
	toMatches := []*security.Match{}
404
	for _, to := range rule.To {
405
		op := to.Operation
406
		if action == security.Action_ALLOW && anyNonEmpty(op.Hosts, op.NotHosts, op.Methods, op.NotMethods, op.Paths, op.NotPaths) {
407
			// L7 policies never match for ALLOW
408
			// For DENY they will always match, so it is more restrictive
409
			return nil
410
		}
411
		match := &security.Match{
412
			DestinationPorts:    stringToPort(op.Ports),
413
			NotDestinationPorts: stringToPort(op.NotPorts),
414
		}
415
		toMatches = append(toMatches, match)
416
	}
417
	fromMatches := []*security.Match{}
418
	for _, from := range rule.From {
419
		op := from.Source
420
		if action == security.Action_ALLOW && anyNonEmpty(op.RemoteIpBlocks, op.NotRemoteIpBlocks, op.RequestPrincipals, op.NotRequestPrincipals) {
421
			// L7 policies never match for ALLOW
422
			// For DENY they will always match, so it is more restrictive
423
			return nil
424
		}
425
		match := &security.Match{
426
			SourceIps:     stringToIP(op.IpBlocks),
427
			NotSourceIps:  stringToIP(op.NotIpBlocks),
428
			Namespaces:    stringToMatch(op.Namespaces),
429
			NotNamespaces: stringToMatch(op.NotNamespaces),
430
			Principals:    stringToMatch(op.Principals),
431
			NotPrincipals: stringToMatch(op.NotPrincipals),
432
		}
433
		fromMatches = append(fromMatches, match)
434
	}
435

436
	rules := []*security.Rules{}
437
	if len(toMatches) > 0 {
438
		rules = append(rules, &security.Rules{Matches: toMatches})
439
	}
440
	if len(fromMatches) > 0 {
441
		rules = append(rules, &security.Rules{Matches: fromMatches})
442
	}
443
	for _, when := range rule.When {
444
		l4 := l4WhenAttributes.Contains(when.Key)
445
		if action == security.Action_ALLOW && !l4 {
446
			// L7 policies never match for ALLOW
447
			// For DENY they will always match, so it is more restrictive
448
			return nil
449
		}
450
		positiveMatch := &security.Match{
451
			Namespaces:       whenMatch("source.namespace", when, false, stringToMatch),
452
			Principals:       whenMatch("source.principal", when, false, stringToMatch),
453
			SourceIps:        whenMatch("source.ip", when, false, stringToIP),
454
			DestinationPorts: whenMatch("destination.port", when, false, stringToPort),
455
			DestinationIps:   whenMatch("destination.ip", when, false, stringToIP),
456

457
			NotNamespaces:       whenMatch("source.namespace", when, true, stringToMatch),
458
			NotPrincipals:       whenMatch("source.principal", when, true, stringToMatch),
459
			NotSourceIps:        whenMatch("source.ip", when, true, stringToIP),
460
			NotDestinationPorts: whenMatch("destination.port", when, true, stringToPort),
461
			NotDestinationIps:   whenMatch("destination.ip", when, true, stringToIP),
462
		}
463
		rules = append(rules, &security.Rules{Matches: []*security.Match{positiveMatch}})
464
	}
465
	return rules
466
}
467

468
var l4WhenAttributes = sets.New(
469
	"source.ip",
470
	"source.namespace",
471
	"source.principal",
472
	"destination.ip",
473
	"destination.port",
474
)
475

476
func whenMatch[T any](s string, when *v1beta1.Condition, invert bool, f func(v []string) []T) []T {
477
	if when.Key != s {
478
		return nil
479
	}
480
	if invert {
481
		return f(when.NotValues)
482
	}
483
	return f(when.Values)
484
}
485

486
func stringToMatch(rules []string) []*security.StringMatch {
487
	res := make([]*security.StringMatch, 0, len(rules))
488
	for _, v := range rules {
489
		var sm *security.StringMatch
490
		switch {
491
		case v == "*":
492
			sm = &security.StringMatch{MatchType: &security.StringMatch_Presence{}}
493
		case strings.HasPrefix(v, "*"):
494
			sm = &security.StringMatch{MatchType: &security.StringMatch_Suffix{
495
				Suffix: strings.TrimPrefix(v, "*"),
496
			}}
497
		case strings.HasSuffix(v, "*"):
498
			sm = &security.StringMatch{MatchType: &security.StringMatch_Prefix{
499
				Prefix: strings.TrimSuffix(v, "*"),
500
			}}
501
		default:
502
			sm = &security.StringMatch{MatchType: &security.StringMatch_Exact{
503
				Exact: v,
504
			}}
505
		}
506
		res = append(res, sm)
507
	}
508
	return res
509
}
510

511
func stringToPort(rules []string) []uint32 {
512
	res := make([]uint32, 0, len(rules))
513
	for _, m := range rules {
514
		p, err := strconv.ParseUint(m, 10, 32)
515
		if err != nil || p > 65535 {
516
			continue
517
		}
518
		res = append(res, uint32(p))
519
	}
520
	return res
521
}
522

523
func stringToIP(rules []string) []*security.Address {
524
	res := make([]*security.Address, 0, len(rules))
525
	for _, m := range rules {
526
		if len(m) == 0 {
527
			continue
528
		}
529

530
		var (
531
			ipAddr        netip.Addr
532
			maxCidrPrefix uint32
533
		)
534

535
		if strings.Contains(m, "/") {
536
			ipp, err := netip.ParsePrefix(m)
537
			if err != nil {
538
				continue
539
			}
540
			ipAddr = ipp.Addr()
541
			maxCidrPrefix = uint32(ipp.Bits())
542
		} else {
543
			ipa, err := netip.ParseAddr(m)
544
			if err != nil {
545
				continue
546
			}
547

548
			ipAddr = ipa
549
			maxCidrPrefix = uint32(ipAddr.BitLen())
550
		}
551

552
		res = append(res, &security.Address{
553
			Address: ipAddr.AsSlice(),
554
			Length:  maxCidrPrefix,
555
		})
556
	}
557
	return res
558
}
559

560
func isMtlsModeUnset(mtls *v1beta1.PeerAuthentication_MutualTLS) bool {
561
	return mtls == nil || mtls.Mode == v1beta1.PeerAuthentication_MutualTLS_UNSET
562
}
563

564
func isMtlsModeStrict(mtls *v1beta1.PeerAuthentication_MutualTLS) bool {
565
	return mtls != nil && mtls.Mode == v1beta1.PeerAuthentication_MutualTLS_STRICT
566
}
567

568
func isMtlsModeDisable(mtls *v1beta1.PeerAuthentication_MutualTLS) bool {
569
	return mtls != nil && mtls.Mode == v1beta1.PeerAuthentication_MutualTLS_DISABLE
570
}
571

572
func isMtlsModePermissive(mtls *v1beta1.PeerAuthentication_MutualTLS) bool {
573
	return mtls != nil && mtls.Mode == v1beta1.PeerAuthentication_MutualTLS_PERMISSIVE
574
}
575

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.