istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/model/envoyfilter.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 model
    16  
    17  import (
    18  	"regexp"
    19  	"strings"
    20  	"time"
    21  
    22  	"google.golang.org/protobuf/proto"
    23  
    24  	networking "istio.io/api/networking/v1alpha3"
    25  	"istio.io/istio/pkg/config"
    26  	"istio.io/istio/pkg/config/labels"
    27  	"istio.io/istio/pkg/config/xds"
    28  	"istio.io/istio/pkg/util/sets"
    29  )
    30  
    31  // EnvoyFilterWrapper is a wrapper for the EnvoyFilter api object with pre-processed data
    32  type EnvoyFilterWrapper struct {
    33  	Name             string
    34  	Namespace        string
    35  	workloadSelector labels.Instance
    36  	Patches          map[networking.EnvoyFilter_ApplyTo][]*EnvoyFilterConfigPatchWrapper
    37  	Priority         int32
    38  	creationTime     time.Time
    39  }
    40  
    41  // EnvoyFilterConfigPatchWrapper is a wrapper over the EnvoyFilter ConfigPatch api object
    42  // fields are ordered such that this struct is aligned
    43  type EnvoyFilterConfigPatchWrapper struct {
    44  	Value     proto.Message
    45  	Match     *networking.EnvoyFilter_EnvoyConfigObjectMatch
    46  	ApplyTo   networking.EnvoyFilter_ApplyTo
    47  	Operation networking.EnvoyFilter_Patch_Operation
    48  	// Pre-compile the regex from proxy version match in the match
    49  	ProxyVersionRegex *regexp.Regexp
    50  	// ProxyPrefixMatch provides a prefix match for the proxy version. The current API only allows
    51  	// regex match, but as an optimization we can reduce this to a prefix match for common cases.
    52  	// If this is set, ProxyVersionRegex is ignored.
    53  	ProxyPrefixMatch string
    54  	Name             string
    55  	Namespace        string
    56  	FullName         string
    57  }
    58  
    59  // wellKnownVersions defines a mapping of well known regex matches to prefix matches
    60  // This is done only as an optimization; behavior should remain the same
    61  // All versions specified by the default installation (Telemetry V2) should be added here.
    62  var wellKnownVersions = map[string]string{
    63  	`^1\.16.*`: "1.16",
    64  	`^1\.17.*`: "1.17",
    65  	`^1\.18.*`: "1.18",
    66  	`^1\.19.*`: "1.19",
    67  	`^1\.20.*`: "1.20",
    68  	`^1\.21.*`: "1.21",
    69  	`^1\.22.*`: "1.22",
    70  	`^1\.23.*`: "1.23",
    71  	// Hopefully we have a better API by 1.23. If not, add it here
    72  }
    73  
    74  // convertToEnvoyFilterWrapper converts from EnvoyFilter config to EnvoyFilterWrapper object
    75  func convertToEnvoyFilterWrapper(local *config.Config) *EnvoyFilterWrapper {
    76  	localEnvoyFilter := local.Spec.(*networking.EnvoyFilter)
    77  
    78  	out := &EnvoyFilterWrapper{Name: local.Name, Namespace: local.Namespace, Priority: localEnvoyFilter.Priority, creationTime: local.CreationTimestamp}
    79  	if localEnvoyFilter.WorkloadSelector != nil {
    80  		out.workloadSelector = localEnvoyFilter.WorkloadSelector.Labels
    81  	}
    82  	out.Patches = make(map[networking.EnvoyFilter_ApplyTo][]*EnvoyFilterConfigPatchWrapper)
    83  	for _, cp := range localEnvoyFilter.ConfigPatches {
    84  		if cp.Patch == nil {
    85  			// Should be caught by validation, but sometimes its disabled and we don't want to crash
    86  			// as a result.
    87  			log.Debugf("envoyfilter %v/%v discarded due to missing patch", local.Namespace, local.Name)
    88  			continue
    89  		}
    90  		cpw := &EnvoyFilterConfigPatchWrapper{
    91  			Name:      local.Name,
    92  			Namespace: local.Namespace,
    93  			FullName:  local.Namespace + "/" + local.Name,
    94  			ApplyTo:   cp.ApplyTo,
    95  			Match:     cp.Match,
    96  			Operation: cp.Patch.Operation,
    97  		}
    98  		var err error
    99  		// Use non-strict building to avoid issues where EnvoyFilter is valid but meant
   100  		// for a different version of the API than we are built with
   101  		cpw.Value, err = xds.BuildXDSObjectFromStruct(cp.ApplyTo, cp.Patch.Value, false)
   102  		// There generally won't be an error here because validation catches mismatched types
   103  		// Should only happen in tests or without validation
   104  		if err != nil {
   105  			log.Errorf("failed to build envoy filter value: %v", err)
   106  			continue
   107  		}
   108  		if cp.Match == nil {
   109  			// create a match all object
   110  			cpw.Match = &networking.EnvoyFilter_EnvoyConfigObjectMatch{Context: networking.EnvoyFilter_ANY}
   111  		} else if cp.Match.Proxy != nil && cp.Match.Proxy.ProxyVersion != "" {
   112  			// Attempt to convert regex to a simple prefix match for the common case of matching
   113  			// a standard Istio version. This field should likely be replaced with semver, but for now
   114  			// we can workaround the performance impact of regex
   115  			if prefix, f := wellKnownVersions[cp.Match.Proxy.ProxyVersion]; f {
   116  				cpw.ProxyPrefixMatch = prefix
   117  			} else {
   118  				// pre-compile the regex for proxy version if it exists
   119  				// ignore the error because validation catches invalid regular expressions.
   120  				cpw.ProxyVersionRegex, _ = regexp.Compile(cp.Match.Proxy.ProxyVersion)
   121  			}
   122  		}
   123  
   124  		if _, exists := out.Patches[cp.ApplyTo]; !exists {
   125  			out.Patches[cp.ApplyTo] = make([]*EnvoyFilterConfigPatchWrapper, 0)
   126  		}
   127  		if cpw.Operation == networking.EnvoyFilter_Patch_INSERT_AFTER ||
   128  			cpw.Operation == networking.EnvoyFilter_Patch_INSERT_BEFORE ||
   129  			cpw.Operation == networking.EnvoyFilter_Patch_INSERT_FIRST {
   130  			// insert_before, after or first is applicable for listener filter, network filter,
   131  			// http filter and http route, convert the rest to add
   132  			if cpw.ApplyTo != networking.EnvoyFilter_HTTP_FILTER &&
   133  				cpw.ApplyTo != networking.EnvoyFilter_NETWORK_FILTER &&
   134  				cpw.ApplyTo != networking.EnvoyFilter_HTTP_ROUTE &&
   135  				cpw.ApplyTo != networking.EnvoyFilter_LISTENER_FILTER {
   136  				cpw.Operation = networking.EnvoyFilter_Patch_ADD
   137  			}
   138  		}
   139  		out.Patches[cp.ApplyTo] = append(out.Patches[cp.ApplyTo], cpw)
   140  	}
   141  	return out
   142  }
   143  
   144  func proxyMatch(proxy *Proxy, cp *EnvoyFilterConfigPatchWrapper) bool {
   145  	if cp.Match.Proxy == nil {
   146  		return true
   147  	}
   148  
   149  	if cp.ProxyPrefixMatch != "" {
   150  		if !strings.HasPrefix(proxy.Metadata.IstioVersion, cp.ProxyPrefixMatch) {
   151  			return false
   152  		}
   153  	}
   154  	if cp.ProxyVersionRegex != nil {
   155  		ver := proxy.Metadata.IstioVersion
   156  		if ver == "" {
   157  			// we do not have a proxy version but the user has a regex. so this is a mismatch
   158  			return false
   159  		}
   160  		if !cp.ProxyVersionRegex.MatchString(ver) {
   161  			return false
   162  		}
   163  	}
   164  
   165  	for k, v := range cp.Match.Proxy.Metadata {
   166  		if proxy.Metadata.Raw[k] != v {
   167  			return false
   168  		}
   169  	}
   170  	return true
   171  }
   172  
   173  // Returns the keys of all the wrapped envoyfilters.
   174  func (efw *EnvoyFilterWrapper) Keys() []string {
   175  	if efw == nil {
   176  		return nil
   177  	}
   178  	keys := sets.String{}
   179  	for _, patches := range efw.Patches {
   180  		for _, patch := range patches {
   181  			keys.Insert(patch.Key())
   182  		}
   183  	}
   184  	return sets.SortedList(keys)
   185  }
   186  
   187  // Returns the keys of all the wrapped envoyfilters.
   188  func (efw *EnvoyFilterWrapper) KeysApplyingTo(applyTo ...networking.EnvoyFilter_ApplyTo) []string {
   189  	if efw == nil {
   190  		return nil
   191  	}
   192  	keys := sets.String{}
   193  	for _, a := range applyTo {
   194  		for _, patch := range efw.Patches[a] {
   195  			keys.Insert(patch.Key())
   196  		}
   197  	}
   198  	return sets.SortedList(keys)
   199  }
   200  
   201  func (cpw *EnvoyFilterConfigPatchWrapper) Key() string {
   202  	if cpw == nil {
   203  		return ""
   204  	}
   205  	return cpw.FullName
   206  }