istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/networking/core/accesslog.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 core 16 17 import ( 18 "sync" 19 20 accesslog "github.com/envoyproxy/go-control-plane/envoy/config/accesslog/v3" 21 core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" 22 listener "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3" 23 cel "github.com/envoyproxy/go-control-plane/envoy/extensions/access_loggers/filters/cel/v3" 24 grpcaccesslog "github.com/envoyproxy/go-control-plane/envoy/extensions/access_loggers/grpc/v3" 25 hcm "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3" 26 tcp "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/tcp_proxy/v3" 27 28 meshconfig "istio.io/api/mesh/v1alpha1" 29 "istio.io/istio/pilot/pkg/model" 30 "istio.io/istio/pilot/pkg/networking" 31 "istio.io/istio/pilot/pkg/util/protoconv" 32 "istio.io/istio/pkg/wellknown" 33 ) 34 35 const ( 36 // EnvoyServerName for istio's envoy 37 EnvoyServerName = "istio-envoy" 38 39 celFilter = "envoy.access_loggers.extension_filters.cel" 40 listenerEnvoyAccessLogFriendlyName = "listener_envoy_accesslog" 41 42 // EnvoyAccessLogCluster is the cluster name that has details for server implementing Envoy ALS. 43 // This cluster is created in bootstrap. 44 EnvoyAccessLogCluster = "envoy_accesslog_service" 45 ) 46 47 var ( 48 // State logged by the metadata exchange filter about the upstream and downstream service instances 49 // We need to propagate these as part of access log service stream 50 // Logging them by default on the console may be an issue as the base64 encoded string is bound to be a big one. 51 // But end users can certainly configure it on their own via the meshConfig using the %FILTER_STATE% macro. 52 envoyWasmStateToLog = []string{"wasm.upstream_peer", "wasm.upstream_peer_id", "wasm.downstream_peer", "wasm.downstream_peer_id"} 53 54 // accessLogBuilder is used to set accessLog to filters 55 accessLogBuilder = newAccessLogBuilder() 56 ) 57 58 type AccessLogBuilder struct { 59 // tcpGrpcAccessLog is used when access log service is enabled in mesh config. 60 tcpGrpcAccessLog *accesslog.AccessLog 61 // httpGrpcAccessLog is used when access log service is enabled in mesh config. 62 httpGrpcAccessLog *accesslog.AccessLog 63 // tcpGrpcListenerAccessLog is used when access log service is enabled in mesh config. 64 tcpGrpcListenerAccessLog *accesslog.AccessLog 65 66 // file accessLog which is cached and reset on MeshConfig change. 67 mutex sync.RWMutex 68 fileAccesslog *accesslog.AccessLog 69 listenerFileAccessLog *accesslog.AccessLog 70 } 71 72 func newAccessLogBuilder() *AccessLogBuilder { 73 return &AccessLogBuilder{ 74 tcpGrpcAccessLog: tcpGrpcAccessLog(false), 75 httpGrpcAccessLog: httpGrpcAccessLog(), 76 tcpGrpcListenerAccessLog: tcpGrpcAccessLog(true), 77 } 78 } 79 80 func (b *AccessLogBuilder) setTCPAccessLog(push *model.PushContext, proxy *model.Proxy, tcp *tcp.TcpProxy, class networking.ListenerClass, svc *model.Service) { 81 mesh := push.Mesh 82 cfgs := push.Telemetry.AccessLogging(push, proxy, class, svc) 83 84 if len(cfgs) == 0 { 85 // No Telemetry API configured, fall back to legacy mesh config setting 86 if mesh.AccessLogFile != "" { 87 tcp.AccessLog = append(tcp.AccessLog, b.buildFileAccessLog(mesh)) 88 } 89 90 if mesh.EnableEnvoyAccessLogService { 91 // Setting it to TCP as the low level one. 92 tcp.AccessLog = append(tcp.AccessLog, b.tcpGrpcAccessLog) 93 } 94 return 95 } 96 97 if al := buildAccessLogFromTelemetry(cfgs, false); len(al) != 0 { 98 tcp.AccessLog = append(tcp.AccessLog, al...) 99 } 100 } 101 102 func buildAccessLogFromTelemetry(cfgs []model.LoggingConfig, forListener bool) []*accesslog.AccessLog { 103 als := make([]*accesslog.AccessLog, 0, len(cfgs)) 104 for _, c := range cfgs { 105 if c.Disabled { 106 continue 107 } 108 filters := make([]*accesslog.AccessLogFilter, 0, 2) 109 if forListener { 110 filters = append(filters, addAccessLogFilter()) 111 } 112 113 if telFilter := buildAccessLogFilterFromTelemetry(c); telFilter != nil { 114 filters = append(filters, telFilter) 115 } 116 117 al := &accesslog.AccessLog{ 118 Name: c.AccessLog.Name, 119 ConfigType: c.AccessLog.ConfigType, 120 Filter: buildAccessLogFilter(filters...), 121 } 122 123 als = append(als, al) 124 } 125 return als 126 } 127 128 func buildAccessLogFilterFromTelemetry(spec model.LoggingConfig) *accesslog.AccessLogFilter { 129 if spec.Filter == nil { 130 return nil 131 } 132 133 fl := &cel.ExpressionFilter{ 134 Expression: spec.Filter.Expression, 135 } 136 137 return &accesslog.AccessLogFilter{ 138 FilterSpecifier: &accesslog.AccessLogFilter_ExtensionFilter{ 139 ExtensionFilter: &accesslog.ExtensionFilter{ 140 Name: celFilter, 141 ConfigType: &accesslog.ExtensionFilter_TypedConfig{TypedConfig: protoconv.MessageToAny(fl)}, 142 }, 143 }, 144 } 145 } 146 147 func (b *AccessLogBuilder) setHTTPAccessLog(push *model.PushContext, proxy *model.Proxy, 148 connectionManager *hcm.HttpConnectionManager, class networking.ListenerClass, svc *model.Service, 149 ) { 150 mesh := push.Mesh 151 cfgs := push.Telemetry.AccessLogging(push, proxy, class, svc) 152 if len(cfgs) == 0 { 153 // No Telemetry API configured, fall back to legacy mesh config setting 154 if mesh.AccessLogFile != "" { 155 connectionManager.AccessLog = append(connectionManager.AccessLog, b.buildFileAccessLog(mesh)) 156 } 157 158 if mesh.EnableEnvoyAccessLogService { 159 connectionManager.AccessLog = append(connectionManager.AccessLog, b.httpGrpcAccessLog) 160 } 161 return 162 } 163 164 if al := buildAccessLogFromTelemetry(cfgs, false); len(al) != 0 { 165 connectionManager.AccessLog = append(connectionManager.AccessLog, al...) 166 } 167 } 168 169 func (b *AccessLogBuilder) setListenerAccessLog(push *model.PushContext, proxy *model.Proxy, 170 listener *listener.Listener, class networking.ListenerClass, 171 ) { 172 mesh := push.Mesh 173 if mesh.DisableEnvoyListenerLog { 174 return 175 } 176 177 cfgs := push.Telemetry.AccessLogging(push, proxy, class, nil) 178 179 if len(cfgs) == 0 { 180 // No Telemetry API configured, fall back to legacy mesh config setting 181 if mesh.AccessLogFile != "" { 182 listener.AccessLog = append(listener.AccessLog, b.buildListenerFileAccessLog(mesh)) 183 } 184 185 if mesh.EnableEnvoyAccessLogService { 186 // Setting it to TCP as the low level one. 187 listener.AccessLog = append(listener.AccessLog, b.tcpGrpcListenerAccessLog) 188 } 189 190 return 191 } 192 193 if al := buildAccessLogFromTelemetry(cfgs, true); len(al) != 0 { 194 listener.AccessLog = append(listener.AccessLog, al...) 195 } 196 } 197 198 func (b *AccessLogBuilder) buildFileAccessLog(mesh *meshconfig.MeshConfig) *accesslog.AccessLog { 199 if cal := b.cachedFileAccessLog(); cal != nil { 200 return cal 201 } 202 203 // We need to build access log. This is needed either on first access or when mesh config changes. 204 al := model.FileAccessLogFromMeshConfig(mesh.AccessLogFile, mesh) 205 206 b.mutex.Lock() 207 defer b.mutex.Unlock() 208 b.fileAccesslog = al 209 210 return al 211 } 212 213 func addAccessLogFilter() *accesslog.AccessLogFilter { 214 return &accesslog.AccessLogFilter{ 215 FilterSpecifier: &accesslog.AccessLogFilter_ResponseFlagFilter{ 216 ResponseFlagFilter: &accesslog.ResponseFlagFilter{Flags: []string{"NR"}}, 217 }, 218 } 219 } 220 221 func buildAccessLogFilter(f ...*accesslog.AccessLogFilter) *accesslog.AccessLogFilter { 222 if len(f) == 0 { 223 return nil 224 } 225 226 if len(f) == 1 { 227 return f[0] 228 } 229 230 return &accesslog.AccessLogFilter{ 231 FilterSpecifier: &accesslog.AccessLogFilter_AndFilter{ 232 AndFilter: &accesslog.AndFilter{ 233 Filters: f, 234 }, 235 }, 236 } 237 } 238 239 func (b *AccessLogBuilder) buildListenerFileAccessLog(mesh *meshconfig.MeshConfig) *accesslog.AccessLog { 240 if cal := b.cachedListenerFileAccessLog(); cal != nil { 241 return cal 242 } 243 244 // We need to build access log. This is needed either on first access or when mesh config changes. 245 lal := model.FileAccessLogFromMeshConfig(mesh.AccessLogFile, mesh) 246 // We add ResponseFlagFilter here, as we want to get listener access logs only on scenarios where we might 247 // not get filter Access Logs like in cases like NR to upstream. 248 lal.Filter = addAccessLogFilter() 249 250 b.mutex.Lock() 251 defer b.mutex.Unlock() 252 b.listenerFileAccessLog = lal 253 254 return lal 255 } 256 257 func (b *AccessLogBuilder) cachedFileAccessLog() *accesslog.AccessLog { 258 b.mutex.RLock() 259 defer b.mutex.RUnlock() 260 return b.fileAccesslog 261 } 262 263 func (b *AccessLogBuilder) cachedListenerFileAccessLog() *accesslog.AccessLog { 264 b.mutex.RLock() 265 defer b.mutex.RUnlock() 266 return b.listenerFileAccessLog 267 } 268 269 func tcpGrpcAccessLog(isListener bool) *accesslog.AccessLog { 270 accessLogFriendlyName := model.TCPEnvoyAccessLogFriendlyName 271 if isListener { 272 accessLogFriendlyName = listenerEnvoyAccessLogFriendlyName 273 } 274 fl := &grpcaccesslog.TcpGrpcAccessLogConfig{ 275 CommonConfig: &grpcaccesslog.CommonGrpcAccessLogConfig{ 276 LogName: accessLogFriendlyName, 277 GrpcService: &core.GrpcService{ 278 TargetSpecifier: &core.GrpcService_EnvoyGrpc_{ 279 EnvoyGrpc: &core.GrpcService_EnvoyGrpc{ 280 ClusterName: EnvoyAccessLogCluster, 281 }, 282 }, 283 }, 284 TransportApiVersion: core.ApiVersion_V3, 285 FilterStateObjectsToLog: envoyWasmStateToLog, 286 }, 287 } 288 289 var filter *accesslog.AccessLogFilter 290 if isListener { 291 filter = addAccessLogFilter() 292 } 293 return &accesslog.AccessLog{ 294 Name: model.TCPEnvoyALSName, 295 ConfigType: &accesslog.AccessLog_TypedConfig{TypedConfig: protoconv.MessageToAny(fl)}, 296 Filter: filter, 297 } 298 } 299 300 func httpGrpcAccessLog() *accesslog.AccessLog { 301 fl := &grpcaccesslog.HttpGrpcAccessLogConfig{ 302 CommonConfig: &grpcaccesslog.CommonGrpcAccessLogConfig{ 303 LogName: model.HTTPEnvoyAccessLogFriendlyName, 304 GrpcService: &core.GrpcService{ 305 TargetSpecifier: &core.GrpcService_EnvoyGrpc_{ 306 EnvoyGrpc: &core.GrpcService_EnvoyGrpc{ 307 ClusterName: EnvoyAccessLogCluster, 308 }, 309 }, 310 }, 311 TransportApiVersion: core.ApiVersion_V3, 312 FilterStateObjectsToLog: envoyWasmStateToLog, 313 }, 314 } 315 316 return &accesslog.AccessLog{ 317 Name: wellknown.HTTPGRPCAccessLog, 318 ConfigType: &accesslog.AccessLog_TypedConfig{TypedConfig: protoconv.MessageToAny(fl)}, 319 } 320 } 321 322 func (b *AccessLogBuilder) reset() { 323 b.mutex.Lock() 324 b.fileAccesslog = nil 325 b.listenerFileAccessLog = nil 326 b.mutex.Unlock() 327 }