istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/config/analysis/analyzers/maturity/maturity.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 maturity
    16  
    17  import (
    18  	"strings"
    19  
    20  	corev1 "k8s.io/api/core/v1"
    21  
    22  	"istio.io/api/annotation"
    23  	"istio.io/istio/pkg/config"
    24  	"istio.io/istio/pkg/config/analysis"
    25  	"istio.io/istio/pkg/config/analysis/analyzers/util"
    26  	"istio.io/istio/pkg/config/analysis/msg"
    27  	"istio.io/istio/pkg/config/constants"
    28  	"istio.io/istio/pkg/config/resource"
    29  	"istio.io/istio/pkg/config/schema/gvk"
    30  )
    31  
    32  // AlphaAnalyzer checks for alpha Istio annotations in K8s resources
    33  type AlphaAnalyzer struct{}
    34  
    35  // the alpha analyzer is currently explicitly left out of the default collection of analyzers to run, as it results
    36  // in too much noise for users, with annotations that are set by default.  Once the noise dies down, this should be
    37  // added to the CombinedAnalyzers() function.
    38  
    39  var istioAnnotations = annotation.AllResourceAnnotations()
    40  
    41  // Metadata implements analyzer.Analyzer
    42  func (*AlphaAnalyzer) Metadata() analysis.Metadata {
    43  	return analysis.Metadata{
    44  		Name:        "annotations.AlphaAnalyzer",
    45  		Description: "Checks for alpha Istio annotations in Kubernetes resources",
    46  		Inputs: []config.GroupVersionKind{
    47  			gvk.Namespace,
    48  			gvk.Service,
    49  			gvk.Pod,
    50  			gvk.Deployment,
    51  		},
    52  	}
    53  }
    54  
    55  var conditionallyIgnoredAnnotations = map[string]bool{
    56  	annotation.SidecarInterceptionMode.Name:               true,
    57  	annotation.SidecarTrafficIncludeInboundPorts.Name:     true,
    58  	annotation.SidecarTrafficExcludeInboundPorts.Name:     true,
    59  	annotation.SidecarTrafficIncludeOutboundIPRanges.Name: true,
    60  }
    61  
    62  var AlwaysIgnoredAnnotations = map[string]bool{
    63  	// this annotation is set by default in istiod, don't alert on it.
    64  	annotation.SidecarStatus.Name: true,
    65  
    66  	// this annotation is set by controller, don't alert on it.
    67  	annotation.GatewayControllerVersion.Name: true,
    68  
    69  	// this annotation is added automatically.
    70  	annotation.IoIstioRev.Name: true,
    71  
    72  	// TODO below are ambient related annotations that are not yet known to be stable.
    73  	// They are added automatically, and should not be alerted on.
    74  	// Delete these related annotations once they are stable.
    75  	// Ref: https://github.com/istio/api/pull/2695
    76  	constants.AmbientWaypointForTrafficTypeLabel: true,
    77  	constants.AmbientRedirection:                 true,
    78  }
    79  
    80  // Analyze implements analysis.Analyzer
    81  func (fa *AlphaAnalyzer) Analyze(ctx analysis.Context) {
    82  	ctx.ForEach(gvk.Namespace, func(r *resource.Instance) bool {
    83  		fa.allowAnnotations(r, ctx, gvk.Namespace)
    84  		return true
    85  	})
    86  	ctx.ForEach(gvk.Service, func(r *resource.Instance) bool {
    87  		fa.allowAnnotations(r, ctx, gvk.Service)
    88  		return true
    89  	})
    90  	ctx.ForEach(gvk.Pod, func(r *resource.Instance) bool {
    91  		fa.allowAnnotations(r, ctx, gvk.Pod)
    92  		return true
    93  	})
    94  	ctx.ForEach(gvk.Deployment, func(r *resource.Instance) bool {
    95  		fa.allowAnnotations(r, ctx, gvk.Deployment)
    96  		return true
    97  	})
    98  }
    99  
   100  func (*AlphaAnalyzer) allowAnnotations(r *resource.Instance, ctx analysis.Context, collectionType config.GroupVersionKind) {
   101  	if len(r.Metadata.Annotations) == 0 {
   102  		return
   103  	}
   104  
   105  	var shouldSkipDefault bool
   106  	if r.Metadata.Schema.GroupVersionKind() == gvk.Pod {
   107  		shouldSkipDefault = isCNIEnabled(r.Message.(*corev1.PodSpec))
   108  	}
   109  
   110  	// It is fine if the annotation is kubectl.kubernetes.io/last-applied-configuration.
   111  	for ann := range r.Metadata.Annotations {
   112  		if !istioAnnotation(ann) {
   113  			continue
   114  		}
   115  
   116  		if annotationDef := lookupAnnotation(ann); annotationDef != nil {
   117  			if annotationDef.FeatureStatus == annotation.Alpha {
   118  				if AlwaysIgnoredAnnotations[annotationDef.Name] {
   119  					continue
   120  				}
   121  				// some annotations are set by default in istiod, don't alert on it.
   122  				if shouldSkipDefault && conditionallyIgnoredAnnotations[annotationDef.Name] {
   123  					continue
   124  				}
   125  				m := msg.NewAlphaAnnotation(r, ann)
   126  				util.AddLineNumber(r, ann, m)
   127  
   128  				ctx.Report(collectionType, m)
   129  			}
   130  		}
   131  	}
   132  }
   133  
   134  func isCNIEnabled(pod *corev1.PodSpec) bool {
   135  	var hasIstioInitContainer bool
   136  	for _, c := range pod.InitContainers {
   137  		if c.Name == "istio-init" {
   138  			hasIstioInitContainer = true
   139  			break
   140  		}
   141  	}
   142  	return !hasIstioInitContainer
   143  }
   144  
   145  // istioAnnotation is true if the annotation is in Istio's namespace
   146  func istioAnnotation(ann string) bool {
   147  	// We document this Kubernetes annotation, we should analyze it as well
   148  	if ann == "kubernetes.io/ingress.class" {
   149  		return true
   150  	}
   151  
   152  	parts := strings.Split(ann, "/")
   153  	if len(parts) == 0 {
   154  		return false
   155  	}
   156  
   157  	if !strings.HasSuffix(parts[0], "istio.io") {
   158  		return false
   159  	}
   160  
   161  	return true
   162  }
   163  
   164  func lookupAnnotation(ann string) *annotation.Instance {
   165  	for _, candidate := range istioAnnotations {
   166  		if candidate.Name == ann {
   167  			return candidate
   168  		}
   169  	}
   170  
   171  	return nil
   172  }