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 }