istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/config/analysis/analyzers/authz/authorizationpolicies.go (about)

     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 authz
    16  
    17  import (
    18  	"fmt"
    19  	"strings"
    20  
    21  	klabels "k8s.io/apimachinery/pkg/labels"
    22  
    23  	"istio.io/api/mesh/v1alpha1"
    24  	"istio.io/api/security/v1beta1"
    25  	"istio.io/istio/pkg/config"
    26  	"istio.io/istio/pkg/config/analysis"
    27  	"istio.io/istio/pkg/config/analysis/analyzers/util"
    28  	"istio.io/istio/pkg/config/analysis/msg"
    29  	"istio.io/istio/pkg/config/resource"
    30  	"istio.io/istio/pkg/config/schema/gvk"
    31  )
    32  
    33  // AuthorizationPoliciesAnalyzer checks the validity of authorization policies
    34  type AuthorizationPoliciesAnalyzer struct{}
    35  
    36  var (
    37  	_          analysis.Analyzer = &AuthorizationPoliciesAnalyzer{}
    38  	meshConfig *v1alpha1.MeshConfig
    39  )
    40  
    41  func (a *AuthorizationPoliciesAnalyzer) Metadata() analysis.Metadata {
    42  	return analysis.Metadata{
    43  		Name:        "auth.AuthorizationPoliciesAnalyzer",
    44  		Description: "Checks the validity of authorization policies",
    45  		Inputs: []config.GroupVersionKind{
    46  			gvk.MeshConfig,
    47  			gvk.AuthorizationPolicy,
    48  			gvk.Namespace,
    49  			gvk.Pod,
    50  		},
    51  	}
    52  }
    53  
    54  func (a *AuthorizationPoliciesAnalyzer) Analyze(c analysis.Context) {
    55  	podLabelsMap := initPodLabelsMap(c)
    56  
    57  	c.ForEach(gvk.AuthorizationPolicy, func(r *resource.Instance) bool {
    58  		a.analyzeNoMatchingWorkloads(r, c, podLabelsMap)
    59  		a.analyzeNamespaceNotFound(r, c)
    60  		return true
    61  	})
    62  }
    63  
    64  func (a *AuthorizationPoliciesAnalyzer) analyzeNoMatchingWorkloads(r *resource.Instance, c analysis.Context, podLabelsMap map[string][]klabels.Set) {
    65  	ap := r.Message.(*v1beta1.AuthorizationPolicy)
    66  	apNs := r.Metadata.FullName.Namespace.String()
    67  
    68  	// If AuthzPolicy is mesh-wide
    69  	if meshWidePolicy(apNs, c) {
    70  		// If it has selector, need further analysis
    71  		if ap.GetSelector() != nil {
    72  			apSelector := klabels.SelectorFromSet(ap.GetSelector().MatchLabels)
    73  			// If there is at least one pod matching the selector within the whole mesh
    74  			if !hasMatchingPodsRunning(apSelector, podLabelsMap) {
    75  				c.Report(gvk.AuthorizationPolicy, msg.NewNoMatchingWorkloadsFound(r, apSelector.String()))
    76  			}
    77  		}
    78  
    79  		// If AuthzPolicy is mesh-wide and selectorless,
    80  		// no need to keep the analysis
    81  		return
    82  	}
    83  
    84  	// If the AuthzPolicy is namespace-wide and there are present Pods,
    85  	// no messages should be triggered.
    86  	if ap.GetSelector() == nil {
    87  		if len(podLabelsMap[apNs]) == 0 {
    88  			c.Report(gvk.AuthorizationPolicy, msg.NewNoMatchingWorkloadsFound(r, ""))
    89  		}
    90  		return
    91  	}
    92  
    93  	// If the AuthzPolicy has Selector, then need to find a matching Pod.
    94  	apSelector := klabels.SelectorFromSet(ap.GetSelector().MatchLabels)
    95  	if !hasMatchingPodsRunningIn(apSelector, podLabelsMap[apNs]) {
    96  		c.Report(gvk.AuthorizationPolicy, msg.NewNoMatchingWorkloadsFound(r, apSelector.String()))
    97  	}
    98  }
    99  
   100  // Returns true when the namespace is the root namespace.
   101  // It takes the MeshConfig names istio, if not the last instance found.
   102  func meshWidePolicy(ns string, c analysis.Context) bool {
   103  	mConf := fetchMeshConfig(c)
   104  	return mConf != nil && ns == mConf.GetRootNamespace()
   105  }
   106  
   107  func fetchMeshConfig(c analysis.Context) *v1alpha1.MeshConfig {
   108  	if meshConfig != nil {
   109  		return meshConfig
   110  	}
   111  
   112  	c.ForEach(gvk.MeshConfig, func(r *resource.Instance) bool {
   113  		meshConfig = r.Message.(*v1alpha1.MeshConfig)
   114  		return r.Metadata.FullName.Name != util.MeshConfigName
   115  	})
   116  
   117  	return meshConfig
   118  }
   119  
   120  func hasMatchingPodsRunning(selector klabels.Selector, podLabelsMap map[string][]klabels.Set) bool {
   121  	for _, setList := range podLabelsMap {
   122  		if hasMatchingPodsRunningIn(selector, setList) {
   123  			return true
   124  		}
   125  	}
   126  	return false
   127  }
   128  
   129  func hasMatchingPodsRunningIn(selector klabels.Selector, setList []klabels.Set) bool {
   130  	hasMatchingPods := false
   131  	for _, labels := range setList {
   132  		if selector.Matches(labels) {
   133  			hasMatchingPods = true
   134  			break
   135  		}
   136  	}
   137  	return hasMatchingPods
   138  }
   139  
   140  func (a *AuthorizationPoliciesAnalyzer) analyzeNamespaceNotFound(r *resource.Instance, c analysis.Context) {
   141  	ap := r.Message.(*v1beta1.AuthorizationPolicy)
   142  
   143  	for i, rule := range ap.Rules {
   144  		for j, from := range rule.From {
   145  			for k, ns := range append(from.Source.Namespaces, from.Source.NotNamespaces...) {
   146  				if !matchNamespace(ns, c) {
   147  					m := msg.NewReferencedResourceNotFound(r, "namespace", ns)
   148  
   149  					nsIndex := k
   150  					if nsIndex >= len(from.Source.Namespaces) {
   151  						nsIndex -= len(from.Source.Namespaces)
   152  					}
   153  
   154  					if line, ok := util.ErrorLine(r, fmt.Sprintf(util.AuthorizationPolicyNameSpace, i, j, nsIndex)); ok {
   155  						m.Line = line
   156  					}
   157  
   158  					c.Report(gvk.AuthorizationPolicy, m)
   159  				}
   160  			}
   161  		}
   162  	}
   163  }
   164  
   165  func matchNamespace(exp string, c analysis.Context) bool {
   166  	match := false
   167  	c.ForEach(gvk.Namespace, func(r *resource.Instance) bool {
   168  		ns := r.Metadata.FullName.String()
   169  		match = namespaceMatch(ns, exp)
   170  		return !match
   171  	})
   172  
   173  	return match
   174  }
   175  
   176  func namespaceMatch(ns, exp string) bool {
   177  	if strings.EqualFold(exp, "*") {
   178  		return true
   179  	}
   180  	if strings.HasPrefix(exp, "*") {
   181  		return strings.HasSuffix(ns, strings.TrimPrefix(exp, "*"))
   182  	}
   183  	if strings.HasSuffix(exp, "*") {
   184  		return strings.HasPrefix(ns, strings.TrimSuffix(exp, "*"))
   185  	}
   186  
   187  	return strings.EqualFold(ns, exp)
   188  }
   189  
   190  // Build a map indexed by namespace with in-mesh Pod's labels
   191  func initPodLabelsMap(c analysis.Context) map[string][]klabels.Set {
   192  	podLabelsMap := make(map[string][]klabels.Set)
   193  
   194  	c.ForEach(gvk.Pod, func(r *resource.Instance) bool {
   195  		pLabels := klabels.Set(r.Metadata.Labels)
   196  
   197  		ns := r.Metadata.FullName.Namespace.String()
   198  		if podLabelsMap[ns] == nil {
   199  			podLabelsMap[ns] = make([]klabels.Set, 0)
   200  		}
   201  
   202  		if util.PodInMesh(r, c) {
   203  			podLabelsMap[ns] = append(podLabelsMap[ns], pLabels)
   204  		}
   205  
   206  		if util.PodInAmbientMode(r) {
   207  			podLabelsMap[ns] = append(podLabelsMap[ns], pLabels)
   208  		}
   209  
   210  		return true
   211  	})
   212  
   213  	return podLabelsMap
   214  }