istio
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
15package ambient
16
17import (
18"fmt"
19"net/netip"
20"strconv"
21"strings"
22
23metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
24
25"istio.io/api/security/v1beta1"
26securityclient "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
35const (
36staticStrictPolicyName = "istio_converted_static_strict" // use '_' character since those are illegal in k8s names
37)
38
39func (a *index) Policies(requested sets.Set[model.ConfigKey]) []model.WorkloadAuthorization {
40// TODO: use many Gets instead of List?
41cfgs := a.authorizationPolicies.List(metav1.NamespaceAll)
42l := len(cfgs)
43if len(requested) > 0 {
44l = len(requested)
45}
46res := make([]model.WorkloadAuthorization, 0, l)
47for _, cfg := range cfgs {
48k := model.ConfigKey{
49Kind: kind.AuthorizationPolicy,
50Name: cfg.Authorization.Name,
51Namespace: cfg.Authorization.Namespace,
52}
53
54if len(requested) > 0 && !requested.Contains(k) {
55continue
56}
57res = append(res, cfg)
58}
59return 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
64func convertedSelectorPeerAuthentications(rootNamespace string, configs []*securityclient.PeerAuthentication) []string {
65var meshCfg, namespaceCfg, workloadCfg *securityclient.PeerAuthentication
66for _, cfg := range configs {
67spec := &cfg.Spec
68if spec.Selector == nil || len(spec.Selector.MatchLabels) == 0 {
69// Namespace-level or mesh-level policy
70if cfg.Namespace == rootNamespace {
71if meshCfg == nil || cfg.CreationTimestamp.Before(&meshCfg.CreationTimestamp) {
72log.Debugf("Switch selected mesh policy to %s.%s (%v)", cfg.Name, cfg.Namespace, cfg.CreationTimestamp)
73meshCfg = cfg
74}
75} else {
76if namespaceCfg == nil || cfg.CreationTimestamp.Before(&namespaceCfg.CreationTimestamp) {
77log.Debugf("Switch selected namespace policy to %s.%s (%v)", cfg.Name, cfg.Namespace, cfg.CreationTimestamp)
78namespaceCfg = cfg
79}
80}
81} else if cfg.Namespace != rootNamespace {
82if workloadCfg == nil || cfg.CreationTimestamp.Before(&workloadCfg.CreationTimestamp) {
83log.Debugf("Switch selected workload policy to %s.%s (%v)", cfg.Name, cfg.Namespace, cfg.CreationTimestamp)
84workloadCfg = 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
91var 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.
94var effectivePortLevelPolicyKey string
95
96// Process in mesh, namespace, workload order to resolve inheritance (UNSET)
97if meshCfg != nil {
98if !isMtlsModeUnset(meshCfg.Spec.Mtls) {
99isEffectiveStrictPolicy = isMtlsModeStrict(meshCfg.Spec.Mtls)
100}
101}
102
103if namespaceCfg != nil {
104if !isMtlsModeUnset(namespaceCfg.Spec.Mtls) {
105isEffectiveStrictPolicy = isMtlsModeStrict(namespaceCfg.Spec.Mtls)
106}
107}
108
109if workloadCfg == nil {
110return effectivePeerAuthenticationKeys(rootNamespace, isEffectiveStrictPolicy, "")
111}
112
113workloadSpec := &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
116if isMtlsModeStrict(workloadSpec.Mtls) {
117isEffectiveStrictPolicy = 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
121if isMtlsModePermissive(workloadSpec.Mtls) || isMtlsModeDisable(workloadSpec.Mtls) {
122isEffectiveStrictPolicy = false
123}
124
125if workloadSpec.PortLevelMtls != nil {
126switch workloadSpec.GetMtls().GetMode() {
127case v1beta1.PeerAuthentication_MutualTLS_STRICT:
128foundPermissive := false
129for _, portMtls := range workloadSpec.PortLevelMtls {
130if isMtlsModePermissive(portMtls) || isMtlsModeDisable(portMtls) {
131foundPermissive = true
132break
133}
134}
135
136if foundPermissive {
137// If we found a non-strict policy, we need to reference this workload policy to see the port level exceptions
138effectivePortLevelPolicyKey = workloadCfg.Namespace + "/" + model.GetAmbientPolicyConfigName(model.ConfigKey{
139Name: workloadCfg.Name,
140Kind: kind.PeerAuthentication,
141Namespace: workloadCfg.Namespace,
142})
143isEffectiveStrictPolicy = false // don't send our static STRICT policy since the converted form of this policy will include the default STRICT mode
144}
145case v1beta1.PeerAuthentication_MutualTLS_PERMISSIVE, v1beta1.PeerAuthentication_MutualTLS_DISABLE:
146foundStrict := false
147for _, portMtls := range workloadSpec.PortLevelMtls {
148if isMtlsModeStrict(portMtls) {
149foundStrict = true
150break
151}
152}
153
154// There's a STRICT port mode, so we need to reference this policy in the workload
155if foundStrict {
156effectivePortLevelPolicyKey = workloadCfg.Namespace + "/" + model.GetAmbientPolicyConfigName(model.ConfigKey{
157Name: workloadCfg.Name,
158Kind: kind.PeerAuthentication,
159Namespace: workloadCfg.Namespace,
160})
161}
162default: // Unset
163if isEffectiveStrictPolicy {
164// Strict mesh or namespace policy
165foundPermissive := false
166for _, portMtls := range workloadSpec.PortLevelMtls {
167if isMtlsModePermissive(portMtls) {
168foundPermissive = true
169break
170}
171}
172
173if foundPermissive {
174// If we found a non-strict policy, we need to reference this workload policy to see the port level exceptions
175effectivePortLevelPolicyKey = workloadCfg.Namespace + "/" + model.GetAmbientPolicyConfigName(model.ConfigKey{
176Name: workloadCfg.Name,
177Kind: kind.PeerAuthentication,
178Namespace: workloadCfg.Namespace,
179})
180}
181} else {
182// Permissive mesh or namespace policy
183isEffectiveStrictPolicy = false // any ports that aren't specified will be PERMISSIVE so this workload isn't effectively under a STRICT policy
184foundStrict := false
185for _, portMtls := range workloadSpec.PortLevelMtls {
186if isMtlsModeStrict(portMtls) {
187foundStrict = true
188continue
189}
190}
191
192// There's a STRICT port mode, so we need to reference this policy in the workload
193if foundStrict {
194effectivePortLevelPolicyKey = workloadCfg.Namespace + "/" + model.GetAmbientPolicyConfigName(model.ConfigKey{
195Name: workloadCfg.Name,
196Kind: kind.PeerAuthentication,
197Namespace: workloadCfg.Namespace,
198})
199}
200}
201}
202}
203
204return effectivePeerAuthenticationKeys(rootNamespace, isEffectiveStrictPolicy, effectivePortLevelPolicyKey)
205}
206
207func effectivePeerAuthenticationKeys(rootNamespace string, isEffectiveStringPolicy bool, effectiveWorkloadPolicyKey string) []string {
208res := sets.New[string]()
209
210if isEffectiveStringPolicy {
211res.Insert(fmt.Sprintf("%s/%s", rootNamespace, staticStrictPolicyName))
212}
213
214if effectiveWorkloadPolicyKey != "" {
215res.Insert(effectiveWorkloadPolicyKey)
216}
217
218return 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)
229func convertPeerAuthentication(rootNamespace string, cfg *securityclient.PeerAuthentication) *security.Authorization {
230pa := &cfg.Spec
231
232mode := pa.GetMtls().GetMode()
233
234scope := security.Scope_WORKLOAD_SELECTOR
235// violates case #1, #2, or #3
236if cfg.Namespace == rootNamespace || pa.Selector == nil || len(pa.PortLevelMtls) == 0 {
237log.Debugf("skipping PeerAuthentication %s/%s for ambient since it isn't a workload policy with port level mTLS", cfg.Namespace, cfg.Name)
238return nil
239}
240
241action := security.Action_DENY
242var rules []*security.Rules
243
244if mode == v1beta1.PeerAuthentication_MutualTLS_STRICT {
245rules = append(rules, &security.Rules{
246Matches: []*security.Match{
247{
248NotPrincipals: []*security.StringMatch{
249{
250MatchType: &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.
262foundNonStrictPortmTLS := false
263for port, mtls := range pa.PortLevelMtls {
264switch portMtlsMode := mtls.GetMode(); {
265case portMtlsMode == v1beta1.PeerAuthentication_MutualTLS_STRICT:
266rules = append(rules, &security.Rules{
267Matches: []*security.Match{
268{
269NotPrincipals: []*security.StringMatch{
270{
271MatchType: &security.StringMatch_Presence{},
272},
273},
274DestinationPorts: []uint32{port},
275},
276},
277})
278case portMtlsMode == v1beta1.PeerAuthentication_MutualTLS_PERMISSIVE:
279// Check top-level mode
280if mode == v1beta1.PeerAuthentication_MutualTLS_PERMISSIVE || mode == v1beta1.PeerAuthentication_MutualTLS_DISABLE {
281// we don't care; log and continue
282log.Debugf("skipping port %s/%s for PeerAuthentication %s/%s for ambient since the parent mTLS mode is %s",
283port, portMtlsMode, cfg.Namespace, cfg.Name, mode)
284continue
285}
286foundNonStrictPortmTLS = 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
289rules = append(rules, &security.Rules{
290Matches: []*security.Match{
291{
292NotDestinationPorts: []uint32{port}, // if the incoming connection does not match this port, deny (notice there's no principals requirement)
293},
294},
295})
296case portMtlsMode == v1beta1.PeerAuthentication_MutualTLS_DISABLE:
297// Check top-level mode
298if mode == v1beta1.PeerAuthentication_MutualTLS_PERMISSIVE || mode == v1beta1.PeerAuthentication_MutualTLS_DISABLE {
299// we don't care; log and continue
300log.Debugf("skipping port %s/%s for PeerAuthentication %s/%s for ambient since the parent mTLS mode is %s",
301port, portMtlsMode, cfg.Namespace, cfg.Name, mode)
302continue
303}
304foundNonStrictPortmTLS = 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
307rules = append(rules, &security.Rules{
308Matches: []*security.Match{
309{
310NotDestinationPorts: []uint32{port}, // if the incoming connection does not match this port, deny (notice there's no principals requirement)
311},
312},
313})
314default:
315log.Debugf("skipping port %s for PeerAuthentication %s/%s for ambient since it is %s", port, cfg.Namespace, cfg.Name, portMtlsMode)
316continue
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
321if mode == v1beta1.PeerAuthentication_MutualTLS_STRICT && !foundNonStrictPortmTLS {
322return nil
323}
324
325if len(rules) == 0 {
326// we never added any rules; return
327return nil
328}
329
330opol := &security.Authorization{
331Name: model.GetAmbientPolicyConfigName(model.ConfigKey{
332Name: cfg.Name,
333Kind: kind.PeerAuthentication,
334Namespace: cfg.Namespace,
335}),
336Namespace: cfg.Namespace,
337Scope: scope,
338Action: action,
339Groups: []*security.Group{{Rules: rules}},
340}
341
342return opol
343}
344
345func convertAuthorizationPolicy(rootns string, obj *securityclient.AuthorizationPolicy) *security.Authorization {
346pol := &obj.Spec
347
348polTargetRef := pol.GetTargetRef()
349if polTargetRef != nil &&
350polTargetRef.Group == gvk.KubernetesGateway.Group &&
351polTargetRef.Kind == gvk.KubernetesGateway.Kind {
352// we have a policy targeting a gateway, do not configure a WDS authorization
353return nil
354}
355
356scope := security.Scope_WORKLOAD_SELECTOR
357if pol.GetSelector() == nil {
358scope = security.Scope_NAMESPACE
359// TODO: TDA
360if rootns == obj.Namespace {
361scope = security.Scope_GLOBAL // TODO: global workload?
362}
363}
364action := security.Action_ALLOW
365switch pol.Action {
366case v1beta1.AuthorizationPolicy_ALLOW:
367case v1beta1.AuthorizationPolicy_DENY:
368action = security.Action_DENY
369default:
370return nil
371}
372opol := &security.Authorization{
373Name: obj.Name,
374Namespace: obj.Namespace,
375Scope: scope,
376Action: action,
377Groups: nil,
378}
379
380for _, rule := range pol.Rules {
381rules := handleRule(action, rule)
382if rules != nil {
383rg := &security.Group{
384Rules: rules,
385}
386opol.Groups = append(opol.Groups, rg)
387}
388}
389
390return opol
391}
392
393func anyNonEmpty[T any](arr ...[]T) bool {
394for _, a := range arr {
395if len(a) > 0 {
396return true
397}
398}
399return false
400}
401
402func handleRule(action security.Action, rule *v1beta1.Rule) []*security.Rules {
403toMatches := []*security.Match{}
404for _, to := range rule.To {
405op := to.Operation
406if 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
409return nil
410}
411match := &security.Match{
412DestinationPorts: stringToPort(op.Ports),
413NotDestinationPorts: stringToPort(op.NotPorts),
414}
415toMatches = append(toMatches, match)
416}
417fromMatches := []*security.Match{}
418for _, from := range rule.From {
419op := from.Source
420if 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
423return nil
424}
425match := &security.Match{
426SourceIps: stringToIP(op.IpBlocks),
427NotSourceIps: stringToIP(op.NotIpBlocks),
428Namespaces: stringToMatch(op.Namespaces),
429NotNamespaces: stringToMatch(op.NotNamespaces),
430Principals: stringToMatch(op.Principals),
431NotPrincipals: stringToMatch(op.NotPrincipals),
432}
433fromMatches = append(fromMatches, match)
434}
435
436rules := []*security.Rules{}
437if len(toMatches) > 0 {
438rules = append(rules, &security.Rules{Matches: toMatches})
439}
440if len(fromMatches) > 0 {
441rules = append(rules, &security.Rules{Matches: fromMatches})
442}
443for _, when := range rule.When {
444l4 := l4WhenAttributes.Contains(when.Key)
445if action == security.Action_ALLOW && !l4 {
446// L7 policies never match for ALLOW
447// For DENY they will always match, so it is more restrictive
448return nil
449}
450positiveMatch := &security.Match{
451Namespaces: whenMatch("source.namespace", when, false, stringToMatch),
452Principals: whenMatch("source.principal", when, false, stringToMatch),
453SourceIps: whenMatch("source.ip", when, false, stringToIP),
454DestinationPorts: whenMatch("destination.port", when, false, stringToPort),
455DestinationIps: whenMatch("destination.ip", when, false, stringToIP),
456
457NotNamespaces: whenMatch("source.namespace", when, true, stringToMatch),
458NotPrincipals: whenMatch("source.principal", when, true, stringToMatch),
459NotSourceIps: whenMatch("source.ip", when, true, stringToIP),
460NotDestinationPorts: whenMatch("destination.port", when, true, stringToPort),
461NotDestinationIps: whenMatch("destination.ip", when, true, stringToIP),
462}
463rules = append(rules, &security.Rules{Matches: []*security.Match{positiveMatch}})
464}
465return rules
466}
467
468var l4WhenAttributes = sets.New(
469"source.ip",
470"source.namespace",
471"source.principal",
472"destination.ip",
473"destination.port",
474)
475
476func whenMatch[T any](s string, when *v1beta1.Condition, invert bool, f func(v []string) []T) []T {
477if when.Key != s {
478return nil
479}
480if invert {
481return f(when.NotValues)
482}
483return f(when.Values)
484}
485
486func stringToMatch(rules []string) []*security.StringMatch {
487res := make([]*security.StringMatch, 0, len(rules))
488for _, v := range rules {
489var sm *security.StringMatch
490switch {
491case v == "*":
492sm = &security.StringMatch{MatchType: &security.StringMatch_Presence{}}
493case strings.HasPrefix(v, "*"):
494sm = &security.StringMatch{MatchType: &security.StringMatch_Suffix{
495Suffix: strings.TrimPrefix(v, "*"),
496}}
497case strings.HasSuffix(v, "*"):
498sm = &security.StringMatch{MatchType: &security.StringMatch_Prefix{
499Prefix: strings.TrimSuffix(v, "*"),
500}}
501default:
502sm = &security.StringMatch{MatchType: &security.StringMatch_Exact{
503Exact: v,
504}}
505}
506res = append(res, sm)
507}
508return res
509}
510
511func stringToPort(rules []string) []uint32 {
512res := make([]uint32, 0, len(rules))
513for _, m := range rules {
514p, err := strconv.ParseUint(m, 10, 32)
515if err != nil || p > 65535 {
516continue
517}
518res = append(res, uint32(p))
519}
520return res
521}
522
523func stringToIP(rules []string) []*security.Address {
524res := make([]*security.Address, 0, len(rules))
525for _, m := range rules {
526if len(m) == 0 {
527continue
528}
529
530var (
531ipAddr netip.Addr
532maxCidrPrefix uint32
533)
534
535if strings.Contains(m, "/") {
536ipp, err := netip.ParsePrefix(m)
537if err != nil {
538continue
539}
540ipAddr = ipp.Addr()
541maxCidrPrefix = uint32(ipp.Bits())
542} else {
543ipa, err := netip.ParseAddr(m)
544if err != nil {
545continue
546}
547
548ipAddr = ipa
549maxCidrPrefix = uint32(ipAddr.BitLen())
550}
551
552res = append(res, &security.Address{
553Address: ipAddr.AsSlice(),
554Length: maxCidrPrefix,
555})
556}
557return res
558}
559
560func isMtlsModeUnset(mtls *v1beta1.PeerAuthentication_MutualTLS) bool {
561return mtls == nil || mtls.Mode == v1beta1.PeerAuthentication_MutualTLS_UNSET
562}
563
564func isMtlsModeStrict(mtls *v1beta1.PeerAuthentication_MutualTLS) bool {
565return mtls != nil && mtls.Mode == v1beta1.PeerAuthentication_MutualTLS_STRICT
566}
567
568func isMtlsModeDisable(mtls *v1beta1.PeerAuthentication_MutualTLS) bool {
569return mtls != nil && mtls.Mode == v1beta1.PeerAuthentication_MutualTLS_DISABLE
570}
571
572func isMtlsModePermissive(mtls *v1beta1.PeerAuthentication_MutualTLS) bool {
573return mtls != nil && mtls.Mode == v1beta1.PeerAuthentication_MutualTLS_PERMISSIVE
574}
575