github.com/cilium/cilium@v1.16.2/pkg/envoy/xds_server.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // Copyright Authors of Cilium 3 4 package envoy 5 6 import ( 7 "context" 8 "fmt" 9 "net" 10 "os" 11 "slices" 12 "sort" 13 "strconv" 14 "strings" 15 16 cilium "github.com/cilium/proxy/go/cilium/api" 17 envoy_mysql_proxy "github.com/cilium/proxy/go/contrib/envoy/extensions/filters/network/mysql_proxy/v3" 18 envoy_config_cluster "github.com/cilium/proxy/go/envoy/config/cluster/v3" 19 envoy_config_core "github.com/cilium/proxy/go/envoy/config/core/v3" 20 envoy_config_endpoint "github.com/cilium/proxy/go/envoy/config/endpoint/v3" 21 envoy_config_listener "github.com/cilium/proxy/go/envoy/config/listener/v3" 22 envoy_config_route "github.com/cilium/proxy/go/envoy/config/route/v3" 23 envoy_extensions_filters_http_router_v3 "github.com/cilium/proxy/go/envoy/extensions/filters/http/router/v3" 24 envoy_upstream_codec "github.com/cilium/proxy/go/envoy/extensions/filters/http/upstream_codec/v3" 25 envoy_extensions_listener_tls_inspector_v3 "github.com/cilium/proxy/go/envoy/extensions/filters/listener/tls_inspector/v3" 26 envoy_config_http "github.com/cilium/proxy/go/envoy/extensions/filters/network/http_connection_manager/v3" 27 envoy_mongo_proxy "github.com/cilium/proxy/go/envoy/extensions/filters/network/mongo_proxy/v3" 28 envoy_config_tcp "github.com/cilium/proxy/go/envoy/extensions/filters/network/tcp_proxy/v3" 29 envoy_config_tls "github.com/cilium/proxy/go/envoy/extensions/transport_sockets/tls/v3" 30 envoy_type_matcher "github.com/cilium/proxy/go/envoy/type/matcher/v3" 31 "github.com/cilium/proxy/pkg/policy/api/kafka" 32 "github.com/sirupsen/logrus" 33 "google.golang.org/protobuf/proto" 34 "google.golang.org/protobuf/types/known/anypb" 35 "google.golang.org/protobuf/types/known/durationpb" 36 "google.golang.org/protobuf/types/known/wrapperspb" 37 38 "github.com/cilium/cilium/pkg/bpf" 39 "github.com/cilium/cilium/pkg/completion" 40 "github.com/cilium/cilium/pkg/crypto/certificatemanager" 41 "github.com/cilium/cilium/pkg/endpointstate" 42 _ "github.com/cilium/cilium/pkg/envoy/resource" 43 "github.com/cilium/cilium/pkg/envoy/xds" 44 "github.com/cilium/cilium/pkg/lock" 45 "github.com/cilium/cilium/pkg/logging/logfields" 46 "github.com/cilium/cilium/pkg/option" 47 "github.com/cilium/cilium/pkg/policy" 48 "github.com/cilium/cilium/pkg/policy/api" 49 "github.com/cilium/cilium/pkg/promise" 50 "github.com/cilium/cilium/pkg/proxy/endpoint" 51 "github.com/cilium/cilium/pkg/time" 52 "github.com/cilium/cilium/pkg/u8proto" 53 ) 54 55 var ( 56 // allowAllPortNetworkPolicy is a PortNetworkPolicy that allows all traffic 57 // to any L4 port. 58 allowAllTCPPortNetworkPolicy = &cilium.PortNetworkPolicy{ 59 // Allow all TCP traffic to any port. 60 Protocol: envoy_config_core.SocketAddress_TCP, 61 } 62 allowAllPortNetworkPolicy = []*cilium.PortNetworkPolicy{ 63 // Allow all TCP traffic to any port. 64 allowAllTCPPortNetworkPolicy, 65 // Allow all UDP/SCTP traffic to any port. 66 // UDP/SCTP rules not sent to Envoy for now. 67 } 68 ) 69 70 const ( 71 CiliumXDSClusterName = "xds-grpc-cilium" 72 73 adminClusterName = "/envoy-admin" 74 egressClusterName = "egress-cluster" 75 egressTLSClusterName = "egress-cluster-tls" 76 ingressClusterName = "ingress-cluster" 77 ingressTLSClusterName = "ingress-cluster-tls" 78 metricsListenerName = "envoy-prometheus-metrics-listener" 79 adminListenerName = "envoy-admin-listener" 80 ) 81 82 type Listener struct { 83 // must hold the xdsServer.mutex when accessing 'count' 84 count uint 85 86 // mutex is needed when accessing the fields below. 87 // xdsServer.mutex is not needed, but if taken it must be taken before 'mutex' 88 mutex lock.RWMutex 89 acked bool 90 nacked bool 91 waiters []*completion.Completion 92 } 93 94 // XDSServer provides a high-lever interface to manage resources published using the xDS gRPC API. 95 type XDSServer interface { 96 // AddListener adds a listener to a running Envoy proxy. 97 AddListener(name string, kind policy.L7ParserType, port uint16, isIngress bool, mayUseOriginalSourceAddr bool, wg *completion.WaitGroup) 98 // AddAdminListener adds an Admin API listener to Envoy. 99 AddAdminListener(port uint16, wg *completion.WaitGroup) 100 // AddMetricsListener adds a prometheus metrics listener to Envoy. 101 AddMetricsListener(port uint16, wg *completion.WaitGroup) 102 // RemoveListener removes an existing Envoy Listener. 103 RemoveListener(name string, wg *completion.WaitGroup) xds.AckingResourceMutatorRevertFunc 104 105 // UpsertEnvoyResources inserts or updates Envoy resources in 'resources' to the xDS cache, 106 // from where they will be delivered to Envoy via xDS streaming gRPC. 107 UpsertEnvoyResources(ctx context.Context, resources Resources) error 108 // UpdateEnvoyResources removes any resources in 'old' that are not 109 // present in 'new' and then adds or updates all resources in 'new'. 110 // Envoy does not support changing the listening port of an existing 111 // listener, so if the port changes we have to delete the old listener 112 // and then add the new one with the new port number. 113 UpdateEnvoyResources(ctx context.Context, old, new Resources) error 114 // DeleteEnvoyResources deletes all Envoy resources in 'resources'. 115 DeleteEnvoyResources(ctx context.Context, resources Resources) error 116 117 // GetNetworkPolicies returns the current version of the network policies with the given names. 118 // If resourceNames is empty, all resources are returned. 119 // 120 // Only used for testing 121 GetNetworkPolicies(resourceNames []string) (map[string]*cilium.NetworkPolicy, error) 122 // UpdateNetworkPolicy adds or updates a network policy in the set published to L7 proxies. 123 // When the proxy acknowledges the network policy update, it will result in 124 // a subsequent call to the endpoint's OnProxyPolicyUpdate() function. 125 UpdateNetworkPolicy(ep endpoint.EndpointUpdater, vis *policy.VisibilityPolicy, policy *policy.L4Policy, ingressPolicyEnforced, egressPolicyEnforced bool, wg *completion.WaitGroup) (error, func() error) 126 // RemoveNetworkPolicy removes network policies relevant to the specified 127 // endpoint from the set published to L7 proxies, and stops listening for 128 // acks for policies on this endpoint. 129 RemoveNetworkPolicy(ep endpoint.EndpointInfoSource) 130 // RemoveAllNetworkPolicies removes all network policies from the set published 131 // to L7 proxies. 132 RemoveAllNetworkPolicies() 133 } 134 135 type xdsServer struct { 136 // socketPath is the path to the gRPC UNIX domain socket. 137 socketPath string 138 139 // accessLogPath is the path to the L7 access logs 140 accessLogPath string 141 142 config xdsServerConfig 143 144 // mutex protects accesses to the configuration resources below. 145 mutex lock.RWMutex 146 147 // listenerMutator publishes listener updates to Envoy proxies. 148 // Manages it's own locking 149 listenerMutator xds.AckingResourceMutator 150 151 // routeMutator publishes route updates to Envoy proxies. 152 // Manages it's own locking 153 routeMutator xds.AckingResourceMutator 154 155 // clusterMutator publishes cluster updates to Envoy proxies. 156 // Manages it's own locking 157 clusterMutator xds.AckingResourceMutator 158 159 // endpointMutator publishes endpoint updates to Envoy proxies. 160 // Manages it's own locking 161 endpointMutator xds.AckingResourceMutator 162 163 // secretMutator publishes secret updates to Envoy proxies. 164 // Manages it's own locking 165 secretMutator xds.AckingResourceMutator 166 167 // listeners is the set of names of listeners that have been added by 168 // calling AddListener. 169 // mutex must be held when accessing this. 170 // Value holds the number of redirects using the listener named by the key. 171 listeners map[string]*Listener 172 173 // proxyListeners is the count of redirection proxy listeners in 'listeners'. 174 // When this is zero, cilium should not wait for NACKs/ACKs from envoy. 175 // This value is different from len(listeners) due to non-proxy listeners 176 // (e.g., prometheus listener) 177 proxyListeners int 178 179 // networkPolicyCache publishes network policy configuration updates to 180 // Envoy proxies. 181 networkPolicyCache *xds.Cache 182 183 // NetworkPolicyMutator wraps networkPolicyCache to publish policy 184 // updates to Envoy proxies. 185 // Exported for testing only! 186 NetworkPolicyMutator xds.AckingResourceMutator 187 188 // stopFunc contains the function which stops the xDS gRPC server. 189 stopFunc context.CancelFunc 190 191 // IPCache is used for tracking IP->Identity mappings and propagating 192 // them to the proxy via NPHDS in the cases described 193 ipCache IPCacheEventSource 194 195 restorerPromise promise.Promise[endpointstate.Restorer] 196 197 localEndpointStore *LocalEndpointStore 198 } 199 200 func toAny(pb proto.Message) *anypb.Any { 201 a, err := anypb.New(pb) 202 if err != nil { 203 panic(err.Error()) 204 } 205 return a 206 } 207 208 type xdsServerConfig struct { 209 envoySocketDir string 210 proxyGID int 211 httpRequestTimeout int 212 httpIdleTimeout int 213 httpMaxGRPCTimeout int 214 httpRetryCount int 215 httpRetryTimeout int 216 httpNormalizePath bool 217 useFullTLSContext bool 218 proxyXffNumTrustedHopsIngress uint32 219 proxyXffNumTrustedHopsEgress uint32 220 } 221 222 // newXDSServer creates a new xDS GRPC server. 223 func newXDSServer(restorerPromise promise.Promise[endpointstate.Restorer], ipCache IPCacheEventSource, localEndpointStore *LocalEndpointStore, config xdsServerConfig) (*xdsServer, error) { 224 return &xdsServer{ 225 restorerPromise: restorerPromise, 226 listeners: make(map[string]*Listener), 227 ipCache: ipCache, 228 localEndpointStore: localEndpointStore, 229 230 socketPath: getXDSSocketPath(config.envoySocketDir), 231 accessLogPath: getAccessLogSocketPath(config.envoySocketDir), 232 config: config, 233 }, nil 234 } 235 236 // start configures and starts the xDS GRPC server. 237 func (s *xdsServer) start() error { 238 socketListener, err := s.newSocketListener() 239 if err != nil { 240 return fmt.Errorf("failed to create socket listener: %w", err) 241 } 242 243 resourceConfig := s.initializeXdsConfigs() 244 245 s.stopFunc = s.startXDSGRPCServer(socketListener, resourceConfig) 246 247 return nil 248 } 249 250 func (s *xdsServer) initializeXdsConfigs() map[string]*xds.ResourceTypeConfiguration { 251 ldsCache := xds.NewCache() 252 ldsMutator := xds.NewAckingResourceMutatorWrapper(ldsCache) 253 ldsConfig := &xds.ResourceTypeConfiguration{ 254 Source: ldsCache, 255 AckObserver: ldsMutator, 256 } 257 258 rdsCache := xds.NewCache() 259 rdsMutator := xds.NewAckingResourceMutatorWrapper(rdsCache) 260 rdsConfig := &xds.ResourceTypeConfiguration{ 261 Source: rdsCache, 262 AckObserver: rdsMutator, 263 } 264 265 cdsCache := xds.NewCache() 266 cdsMutator := xds.NewAckingResourceMutatorWrapper(cdsCache) 267 cdsConfig := &xds.ResourceTypeConfiguration{ 268 Source: cdsCache, 269 AckObserver: cdsMutator, 270 } 271 272 edsCache := xds.NewCache() 273 edsMutator := xds.NewAckingResourceMutatorWrapper(edsCache) 274 edsConfig := &xds.ResourceTypeConfiguration{ 275 Source: edsCache, 276 AckObserver: edsMutator, 277 } 278 279 sdsCache := xds.NewCache() 280 sdsMutator := xds.NewAckingResourceMutatorWrapper(sdsCache) 281 sdsConfig := &xds.ResourceTypeConfiguration{ 282 Source: sdsCache, 283 AckObserver: sdsMutator, 284 } 285 286 npdsCache := xds.NewCache() 287 npdsMutator := xds.NewAckingResourceMutatorWrapper(npdsCache) 288 npdsConfig := &xds.ResourceTypeConfiguration{ 289 Source: npdsCache, 290 AckObserver: npdsMutator, 291 } 292 293 nphdsCache := newNPHDSCache(s.ipCache) 294 nphdsConfig := &xds.ResourceTypeConfiguration{ 295 Source: nphdsCache, 296 AckObserver: &nphdsCache, 297 } 298 299 s.listenerMutator = ldsMutator 300 s.routeMutator = rdsMutator 301 s.clusterMutator = cdsMutator 302 s.endpointMutator = edsMutator 303 s.secretMutator = sdsMutator 304 s.networkPolicyCache = npdsCache 305 s.NetworkPolicyMutator = npdsMutator 306 307 resourceConfig := map[string]*xds.ResourceTypeConfiguration{ 308 ListenerTypeURL: ldsConfig, 309 RouteTypeURL: rdsConfig, 310 ClusterTypeURL: cdsConfig, 311 EndpointTypeURL: edsConfig, 312 SecretTypeURL: sdsConfig, 313 NetworkPolicyTypeURL: npdsConfig, 314 NetworkPolicyHostsTypeURL: nphdsConfig, 315 } 316 return resourceConfig 317 } 318 319 func (s *xdsServer) newSocketListener() (*net.UnixListener, error) { 320 // Remove/Unlink the old unix domain socket, if any. 321 _ = os.Remove(s.socketPath) 322 323 socketListener, err := net.ListenUnix("unix", &net.UnixAddr{Name: s.socketPath, Net: "unix"}) 324 if err != nil { 325 return nil, fmt.Errorf("failed to open xDS listen socket at %s: %w", s.socketPath, err) 326 } 327 328 // Make the socket accessible by owner and group only. 329 if err = os.Chmod(s.socketPath, 0660); err != nil { 330 return nil, fmt.Errorf("failed to change mode of xDS listen socket at %s: %w", s.socketPath, err) 331 } 332 // Change the group to ProxyGID allowing access from any process from that group. 333 if err = os.Chown(s.socketPath, -1, s.config.proxyGID); err != nil { 334 log.WithError(err).Warningf("Envoy: Failed to change the group of xDS listen socket at %s", s.socketPath) 335 } 336 return socketListener, nil 337 } 338 339 func (s *xdsServer) stop() { 340 if s.stopFunc != nil { 341 s.stopFunc() 342 } 343 if s.socketPath != "" { 344 _ = os.Remove(s.socketPath) 345 } 346 } 347 348 func GetCiliumHttpFilter() *envoy_config_http.HttpFilter { 349 return &envoy_config_http.HttpFilter{ 350 Name: "cilium.l7policy", 351 ConfigType: &envoy_config_http.HttpFilter_TypedConfig{ 352 TypedConfig: toAny(&cilium.L7Policy{ 353 AccessLogPath: getAccessLogSocketPath(GetSocketDir(option.Config.RunDir)), 354 Denied_403Body: option.Config.HTTP403Message, 355 }), 356 }, 357 } 358 } 359 360 func GetUpstreamCodecFilter() *envoy_config_http.HttpFilter { 361 return &envoy_config_http.HttpFilter{ 362 Name: "envoy.filters.http.upstream_codec", 363 ConfigType: &envoy_config_http.HttpFilter_TypedConfig{ 364 TypedConfig: toAny(&envoy_upstream_codec.UpstreamCodec{}), 365 }, 366 } 367 } 368 369 func (s *xdsServer) getHttpFilterChainProto(clusterName string, tls bool, isIngress bool) *envoy_config_listener.FilterChain { 370 371 requestTimeout := int64(s.config.httpRequestTimeout) // seconds 372 idleTimeout := int64(s.config.httpIdleTimeout) // seconds 373 maxGRPCTimeout := int64(s.config.httpMaxGRPCTimeout) // seconds 374 numRetries := uint32(s.config.httpRetryCount) 375 retryTimeout := int64(s.config.httpRetryTimeout) // seconds 376 xffNumTrustedHops := s.config.proxyXffNumTrustedHopsEgress 377 if isIngress { 378 xffNumTrustedHops = s.config.proxyXffNumTrustedHopsIngress 379 } 380 381 hcmConfig := &envoy_config_http.HttpConnectionManager{ 382 StatPrefix: "proxy", 383 UseRemoteAddress: &wrapperspb.BoolValue{Value: true}, 384 SkipXffAppend: true, 385 XffNumTrustedHops: xffNumTrustedHops, 386 HttpFilters: []*envoy_config_http.HttpFilter{ 387 GetCiliumHttpFilter(), 388 { 389 Name: "envoy.filters.http.router", 390 ConfigType: &envoy_config_http.HttpFilter_TypedConfig{ 391 TypedConfig: toAny(&envoy_extensions_filters_http_router_v3.Router{}), 392 }, 393 }, 394 }, 395 StreamIdleTimeout: &durationpb.Duration{}, // 0 == disabled 396 RouteSpecifier: &envoy_config_http.HttpConnectionManager_RouteConfig{ 397 RouteConfig: &envoy_config_route.RouteConfiguration{ 398 VirtualHosts: []*envoy_config_route.VirtualHost{{ 399 Name: "default_route", 400 Domains: []string{"*"}, 401 Routes: []*envoy_config_route.Route{{ 402 Match: &envoy_config_route.RouteMatch{ 403 PathSpecifier: &envoy_config_route.RouteMatch_Prefix{Prefix: "/"}, 404 Grpc: &envoy_config_route.RouteMatch_GrpcRouteMatchOptions{}, 405 }, 406 Action: &envoy_config_route.Route_Route{ 407 Route: &envoy_config_route.RouteAction{ 408 ClusterSpecifier: &envoy_config_route.RouteAction_Cluster{ 409 Cluster: clusterName, 410 }, 411 Timeout: &durationpb.Duration{Seconds: requestTimeout}, 412 MaxStreamDuration: &envoy_config_route.RouteAction_MaxStreamDuration{ 413 GrpcTimeoutHeaderMax: &durationpb.Duration{Seconds: maxGRPCTimeout}, 414 }, 415 RetryPolicy: &envoy_config_route.RetryPolicy{ 416 RetryOn: "5xx", 417 NumRetries: &wrapperspb.UInt32Value{Value: numRetries}, 418 PerTryTimeout: &durationpb.Duration{Seconds: retryTimeout}, 419 }, 420 }, 421 }, 422 }, { 423 Match: &envoy_config_route.RouteMatch{ 424 PathSpecifier: &envoy_config_route.RouteMatch_Prefix{Prefix: "/"}, 425 }, 426 Action: &envoy_config_route.Route_Route{ 427 Route: &envoy_config_route.RouteAction{ 428 ClusterSpecifier: &envoy_config_route.RouteAction_Cluster{ 429 Cluster: clusterName, 430 }, 431 Timeout: &durationpb.Duration{Seconds: requestTimeout}, 432 // IdleTimeout: &durationpb.Duration{Seconds: idleTimeout}, 433 RetryPolicy: &envoy_config_route.RetryPolicy{ 434 RetryOn: "5xx", 435 NumRetries: &wrapperspb.UInt32Value{Value: numRetries}, 436 PerTryTimeout: &durationpb.Duration{Seconds: retryTimeout}, 437 }, 438 }, 439 }, 440 }}, 441 }}, 442 }, 443 }, 444 } 445 446 if s.config.httpNormalizePath { 447 hcmConfig.NormalizePath = &wrapperspb.BoolValue{Value: true} 448 hcmConfig.MergeSlashes = true 449 hcmConfig.PathWithEscapedSlashesAction = envoy_config_http.HttpConnectionManager_UNESCAPE_AND_REDIRECT 450 } 451 452 // Idle timeout can only be specified if non-zero 453 if idleTimeout > 0 { 454 hcmConfig.GetRouteConfig().VirtualHosts[0].Routes[1].GetRoute().IdleTimeout = &durationpb.Duration{Seconds: idleTimeout} 455 } 456 457 chain := &envoy_config_listener.FilterChain{ 458 Filters: []*envoy_config_listener.Filter{{ 459 Name: "cilium.network", 460 ConfigType: &envoy_config_listener.Filter_TypedConfig{ 461 TypedConfig: toAny(&cilium.NetworkFilter{}), 462 }, 463 }, { 464 Name: "envoy.filters.network.http_connection_manager", 465 ConfigType: &envoy_config_listener.Filter_TypedConfig{ 466 TypedConfig: toAny(hcmConfig), 467 }, 468 }}, 469 } 470 471 if tls { 472 chain.FilterChainMatch = &envoy_config_listener.FilterChainMatch{ 473 TransportProtocol: "tls", 474 } 475 chain.TransportSocket = &envoy_config_core.TransportSocket{ 476 Name: "cilium.tls_wrapper", 477 ConfigType: &envoy_config_core.TransportSocket_TypedConfig{ 478 TypedConfig: toAny(&cilium.DownstreamTlsWrapperContext{}), 479 }, 480 } 481 } 482 483 return chain 484 } 485 486 // getTcpFilterChainProto creates a TCP filter chain with the Cilium network filter. 487 // By default, the returned chain can be used with the Cilium Go extensions L7 parsers 488 // in 'proxylib' directory in the Cilium repo. 489 // When optional 'filterName' is given, it is configured as the first filter in the chain 490 // and 'proxylib' is not configured. In this case the returned filter chain is only used 491 // if the applicable network policy specifies 'filterName' as the L7 parser. 492 func (s *xdsServer) getTcpFilterChainProto(clusterName string, filterName string, config *anypb.Any, tls bool) *envoy_config_listener.FilterChain { 493 var filters []*envoy_config_listener.Filter 494 495 // 1. Add the filter 'filterName' to the beginning of the TCP chain with optional 'config', if needed. 496 if filterName != "" { 497 filter := &envoy_config_listener.Filter{Name: filterName} 498 if config != nil { 499 filter.ConfigType = &envoy_config_listener.Filter_TypedConfig{ 500 TypedConfig: config, 501 } 502 } 503 filters = append(filters, filter) 504 } 505 506 // 2. Add Cilium Network filter. 507 var ciliumConfig *cilium.NetworkFilter 508 if filterName == "" { 509 // Use proxylib by default 510 ciliumConfig = &cilium.NetworkFilter{ 511 Proxylib: "libcilium.so", 512 ProxylibParams: map[string]string{ 513 "access-log-path": s.accessLogPath, 514 "xds-path": s.socketPath, 515 }, 516 } 517 } else { 518 // Envoy metadata logging requires accesslog path 519 ciliumConfig = &cilium.NetworkFilter{ 520 AccessLogPath: s.accessLogPath, 521 } 522 } 523 filters = append(filters, &envoy_config_listener.Filter{ 524 Name: "cilium.network", 525 ConfigType: &envoy_config_listener.Filter_TypedConfig{ 526 TypedConfig: toAny(ciliumConfig), 527 }, 528 }) 529 530 // 3. Add the TCP proxy filter. 531 filters = append(filters, &envoy_config_listener.Filter{ 532 Name: "envoy.filters.network.tcp_proxy", 533 ConfigType: &envoy_config_listener.Filter_TypedConfig{ 534 TypedConfig: toAny(&envoy_config_tcp.TcpProxy{ 535 StatPrefix: "tcp_proxy", 536 ClusterSpecifier: &envoy_config_tcp.TcpProxy_Cluster{ 537 Cluster: clusterName, 538 }, 539 }), 540 }, 541 }) 542 543 chain := &envoy_config_listener.FilterChain{ 544 Filters: filters, 545 } 546 547 if tls { 548 chain.FilterChainMatch = &envoy_config_listener.FilterChainMatch{ 549 TransportProtocol: "tls", 550 } 551 chain.TransportSocket = &envoy_config_core.TransportSocket{ 552 Name: "cilium.tls_wrapper", 553 ConfigType: &envoy_config_core.TransportSocket_TypedConfig{ 554 TypedConfig: toAny(&cilium.DownstreamTlsWrapperContext{}), 555 }, 556 } 557 } else { 558 chain.FilterChainMatch = &envoy_config_listener.FilterChainMatch{ 559 // must have transport match for non-TLS, 560 // otherwise TLS inspector will be automatically inserted 561 TransportProtocol: "raw_buffer", 562 } 563 } 564 565 if filterName != "" { 566 // Add filter chain match for 'filterName' so that connections for which policy says to use this L7 567 // are handled by this filter chain. 568 chain.FilterChainMatch.ApplicationProtocols = []string{filterName} 569 } 570 571 return chain 572 } 573 574 func getPublicListenerAddress(port uint16, ipv4, ipv6 bool) *envoy_config_core.Address { 575 listenerAddr := "0.0.0.0" 576 if ipv6 { 577 listenerAddr = "::" 578 } 579 return &envoy_config_core.Address{ 580 Address: &envoy_config_core.Address_SocketAddress{ 581 SocketAddress: &envoy_config_core.SocketAddress{ 582 Protocol: envoy_config_core.SocketAddress_TCP, 583 Address: listenerAddr, 584 Ipv4Compat: ipv4 && ipv6, 585 PortSpecifier: &envoy_config_core.SocketAddress_PortValue{PortValue: uint32(port)}, 586 }, 587 }, 588 } 589 } 590 591 func GetLocalListenerAddresses(port uint16, ipv4, ipv6 bool) (*envoy_config_core.Address, []*envoy_config_listener.AdditionalAddress) { 592 addresses := []*envoy_config_core.Address_SocketAddress{} 593 594 if ipv4 { 595 addresses = append(addresses, &envoy_config_core.Address_SocketAddress{ 596 SocketAddress: &envoy_config_core.SocketAddress{ 597 Protocol: envoy_config_core.SocketAddress_TCP, 598 Address: "127.0.0.1", 599 PortSpecifier: &envoy_config_core.SocketAddress_PortValue{PortValue: uint32(port)}, 600 }, 601 }) 602 } 603 604 if ipv6 { 605 addresses = append(addresses, &envoy_config_core.Address_SocketAddress{ 606 SocketAddress: &envoy_config_core.SocketAddress{ 607 Protocol: envoy_config_core.SocketAddress_TCP, 608 Address: "::1", 609 PortSpecifier: &envoy_config_core.SocketAddress_PortValue{PortValue: uint32(port)}, 610 }, 611 }) 612 } 613 614 var additionalAddress []*envoy_config_listener.AdditionalAddress 615 616 if len(addresses) > 1 { 617 additionalAddress = append(additionalAddress, &envoy_config_listener.AdditionalAddress{ 618 Address: &envoy_config_core.Address{ 619 Address: addresses[1], 620 }, 621 }) 622 } 623 624 return &envoy_config_core.Address{ 625 Address: addresses[0], 626 }, additionalAddress 627 } 628 629 func (s *xdsServer) AddAdminListener(port uint16, wg *completion.WaitGroup) { 630 if port == 0 { 631 return // 0 == disabled 632 } 633 log.WithField(logfields.Port, port).Debug("Envoy: AddAdminListener") 634 635 s.addListener(adminListenerName, func() *envoy_config_listener.Listener { 636 hcmConfig := &envoy_config_http.HttpConnectionManager{ 637 StatPrefix: adminListenerName, 638 UseRemoteAddress: &wrapperspb.BoolValue{Value: true}, 639 SkipXffAppend: true, 640 HttpFilters: []*envoy_config_http.HttpFilter{{ 641 Name: "envoy.filters.http.router", 642 ConfigType: &envoy_config_http.HttpFilter_TypedConfig{ 643 TypedConfig: toAny(&envoy_extensions_filters_http_router_v3.Router{}), 644 }, 645 }}, 646 StreamIdleTimeout: &durationpb.Duration{}, // 0 == disabled 647 RouteSpecifier: &envoy_config_http.HttpConnectionManager_RouteConfig{ 648 RouteConfig: &envoy_config_route.RouteConfiguration{ 649 VirtualHosts: []*envoy_config_route.VirtualHost{{ 650 Name: "admin_listener_route", 651 Domains: []string{"*"}, 652 Routes: []*envoy_config_route.Route{{ 653 Match: &envoy_config_route.RouteMatch{ 654 PathSpecifier: &envoy_config_route.RouteMatch_Prefix{Prefix: "/"}, 655 }, 656 Action: &envoy_config_route.Route_Route{ 657 Route: &envoy_config_route.RouteAction{ 658 ClusterSpecifier: &envoy_config_route.RouteAction_Cluster{ 659 Cluster: adminClusterName, 660 }, 661 }, 662 }, 663 }}, 664 }}, 665 }, 666 }, 667 } 668 669 addr, additionalAddr := GetLocalListenerAddresses(port, option.Config.IPv4Enabled(), option.Config.IPv6Enabled()) 670 listenerConf := &envoy_config_listener.Listener{ 671 Name: adminListenerName, 672 Address: addr, 673 AdditionalAddresses: additionalAddr, 674 FilterChains: []*envoy_config_listener.FilterChain{{ 675 Filters: []*envoy_config_listener.Filter{{ 676 Name: "envoy.filters.network.http_connection_manager", 677 ConfigType: &envoy_config_listener.Filter_TypedConfig{ 678 TypedConfig: toAny(hcmConfig), 679 }, 680 }}, 681 }}, 682 } 683 684 return listenerConf 685 }, wg, func(err error) { 686 if err != nil { 687 log.WithField(logfields.Port, port).WithError(err).Debug("Envoy: Adding admin listener failed") 688 // Remove the added listener in case of a failure 689 s.removeListener(adminListenerName, nil, false) 690 } else { 691 log.WithField(logfields.Port, port).Info("Envoy: Listening for Admin API") 692 } 693 }, false) 694 } 695 696 func (s *xdsServer) AddMetricsListener(port uint16, wg *completion.WaitGroup) { 697 if port == 0 { 698 return // 0 == disabled 699 } 700 log.WithField(logfields.Port, port).Debug("Envoy: AddMetricsListener") 701 702 s.addListener(metricsListenerName, func() *envoy_config_listener.Listener { 703 hcmConfig := &envoy_config_http.HttpConnectionManager{ 704 StatPrefix: metricsListenerName, 705 UseRemoteAddress: &wrapperspb.BoolValue{Value: true}, 706 SkipXffAppend: true, 707 HttpFilters: []*envoy_config_http.HttpFilter{{ 708 Name: "envoy.filters.http.router", 709 ConfigType: &envoy_config_http.HttpFilter_TypedConfig{ 710 TypedConfig: toAny(&envoy_extensions_filters_http_router_v3.Router{}), 711 }, 712 }}, 713 StreamIdleTimeout: &durationpb.Duration{}, // 0 == disabled 714 RouteSpecifier: &envoy_config_http.HttpConnectionManager_RouteConfig{ 715 RouteConfig: &envoy_config_route.RouteConfiguration{ 716 VirtualHosts: []*envoy_config_route.VirtualHost{{ 717 Name: "prometheus_metrics_route", 718 Domains: []string{"*"}, 719 Routes: []*envoy_config_route.Route{{ 720 Match: &envoy_config_route.RouteMatch{ 721 PathSpecifier: &envoy_config_route.RouteMatch_Prefix{Prefix: "/metrics"}, 722 }, 723 Action: &envoy_config_route.Route_Route{ 724 Route: &envoy_config_route.RouteAction{ 725 ClusterSpecifier: &envoy_config_route.RouteAction_Cluster{ 726 Cluster: adminClusterName, 727 }, 728 PrefixRewrite: "/stats/prometheus", 729 }, 730 }, 731 }}, 732 }}, 733 }, 734 }, 735 } 736 737 listenerConf := &envoy_config_listener.Listener{ 738 Name: metricsListenerName, 739 Address: getPublicListenerAddress(port, option.Config.IPv4Enabled(), option.Config.IPv6Enabled()), 740 FilterChains: []*envoy_config_listener.FilterChain{{ 741 Filters: []*envoy_config_listener.Filter{{ 742 Name: "envoy.filters.network.http_connection_manager", 743 ConfigType: &envoy_config_listener.Filter_TypedConfig{ 744 TypedConfig: toAny(hcmConfig), 745 }, 746 }}, 747 }}, 748 } 749 750 return listenerConf 751 }, wg, func(err error) { 752 if err != nil { 753 log.WithField(logfields.Port, port).WithError(err).Debug("Envoy: Adding metrics listener failed") 754 // Remove the added listener in case of a failure 755 s.removeListener(metricsListenerName, nil, false) 756 } else { 757 log.WithField(logfields.Port, port).Info("Envoy: Listening for prometheus metrics") 758 } 759 }, false) 760 } 761 762 // addListener either reuses an existing listener with 'name', or creates a new one. 763 // 'listenerConf()' is only called if a new listener is being created. 764 func (s *xdsServer) addListener(name string, listenerConf func() *envoy_config_listener.Listener, wg *completion.WaitGroup, cb func(err error), isProxyListener bool) { 765 s.mutex.Lock() 766 defer s.mutex.Unlock() 767 768 listener := s.listeners[name] 769 if listener == nil { 770 listener = &Listener{} 771 s.listeners[name] = listener 772 if isProxyListener { 773 s.proxyListeners++ 774 } 775 } 776 listener.count++ 777 listener.mutex.Lock() // needed for other than 'count' 778 if listener.count > 1 && !listener.nacked { 779 log.Debugf("Envoy: Reusing listener: %s", name) 780 call := true 781 if !listener.acked { 782 // Listener not acked yet, add a completion to the waiter's list 783 log.Debugf("Envoy: Waiting for a non-acknowledged reused listener: %s", name) 784 listener.waiters = append(listener.waiters, wg.AddCompletionWithCallback(cb)) 785 call = false 786 } 787 listener.mutex.Unlock() 788 789 // call the callback with nil error if the listener was acked already 790 if call && cb != nil { 791 cb(nil) 792 } 793 return 794 } 795 // Try again after a NACK, potentially with a different port number, etc. 796 if listener.nacked { 797 listener.acked = false 798 listener.nacked = false 799 } 800 listener.mutex.Unlock() // Listener locked again in callbacks below 801 802 listenerConfig := listenerConf() 803 if option.Config.EnableBPFTProxy { 804 // Envoy since 1.20.0 uses SO_REUSEPORT on listeners by default. 805 // BPF TPROXY is currently not compatible with SO_REUSEPORT, so disable it. 806 // Note that this may degrade Envoy performance. 807 listenerConfig.EnableReusePort = &wrapperspb.BoolValue{Value: false} 808 } 809 if err := listenerConfig.Validate(); err != nil { 810 log.Errorf("Envoy: Could not validate Listener (%s): %s", err, listenerConfig.String()) 811 if cb != nil { 812 cb(err) 813 } 814 return 815 } 816 817 s.listenerMutator.Upsert(ListenerTypeURL, name, listenerConfig, []string{"127.0.0.1"}, wg, 818 func(err error) { 819 // listener might have already been removed, so we can't look again 820 // but we still need to complete all the completions in case 821 // someone is still waiting! 822 listener.mutex.Lock() 823 if err == nil { 824 // Allow future users to not need to wait 825 listener.acked = true 826 } else { 827 // Prevent further reuse of a failed listener 828 listener.nacked = true 829 } 830 // Pass the completion result to all the additional waiters. 831 for _, waiter := range listener.waiters { 832 _ = waiter.Complete(err) 833 } 834 listener.waiters = nil 835 listener.mutex.Unlock() 836 837 if cb != nil { 838 cb(err) 839 } 840 }) 841 } 842 843 // upsertListener either updates an existing LDS listener with 'name', or creates a new one. 844 func (s *xdsServer) upsertListener(name string, listenerConf *envoy_config_listener.Listener, wg *completion.WaitGroup, callback func(error)) xds.AckingResourceMutatorRevertFunc { 845 s.mutex.Lock() 846 defer s.mutex.Unlock() 847 // 'callback' is not called if there is no change and this configuration has already been acked. 848 return s.listenerMutator.Upsert(ListenerTypeURL, name, listenerConf, []string{"127.0.0.1"}, wg, callback) 849 } 850 851 // deleteListener deletes an LDS Envoy Listener. 852 func (s *xdsServer) deleteListener(name string, wg *completion.WaitGroup, callback func(error)) xds.AckingResourceMutatorRevertFunc { 853 s.mutex.Lock() 854 defer s.mutex.Unlock() 855 // 'callback' is not called if there is no change and this configuration has already been acked. 856 return s.listenerMutator.Delete(ListenerTypeURL, name, []string{"127.0.0.1"}, wg, callback) 857 } 858 859 // upsertRoute either updates an existing RDS route with 'name', or creates a new one. 860 func (s *xdsServer) upsertRoute(name string, conf *envoy_config_route.RouteConfiguration, wg *completion.WaitGroup, callback func(error)) xds.AckingResourceMutatorRevertFunc { 861 s.mutex.Lock() 862 defer s.mutex.Unlock() 863 // 'callback' is not called if there is no change and this configuration has already been acked. 864 return s.routeMutator.Upsert(RouteTypeURL, name, conf, []string{"127.0.0.1"}, wg, callback) 865 } 866 867 // deleteRoute deletes an RDS Route. 868 func (s *xdsServer) deleteRoute(name string, wg *completion.WaitGroup, callback func(error)) xds.AckingResourceMutatorRevertFunc { 869 s.mutex.Lock() 870 defer s.mutex.Unlock() 871 // 'callback' is not called if there is no change and this configuration has already been acked. 872 return s.routeMutator.Delete(RouteTypeURL, name, []string{"127.0.0.1"}, wg, callback) 873 } 874 875 // upsertCluster either updates an existing CDS cluster with 'name', or creates a new one. 876 func (s *xdsServer) upsertCluster(name string, conf *envoy_config_cluster.Cluster, wg *completion.WaitGroup, callback func(error)) xds.AckingResourceMutatorRevertFunc { 877 s.mutex.Lock() 878 defer s.mutex.Unlock() 879 // 'callback' is not called if there is no change and this configuration has already been acked. 880 return s.clusterMutator.Upsert(ClusterTypeURL, name, conf, []string{"127.0.0.1"}, wg, callback) 881 } 882 883 // deleteCluster deletes an CDS cluster. 884 func (s *xdsServer) deleteCluster(name string, wg *completion.WaitGroup, callback func(error)) xds.AckingResourceMutatorRevertFunc { 885 s.mutex.Lock() 886 defer s.mutex.Unlock() 887 // 'callback' is not called if there is no change and this configuration has already been acked. 888 return s.clusterMutator.Delete(ClusterTypeURL, name, []string{"127.0.0.1"}, wg, callback) 889 } 890 891 // upsertEndpoint either updates an existing EDS endpoint with 'name', or creates a new one. 892 func (s *xdsServer) upsertEndpoint(name string, conf *envoy_config_endpoint.ClusterLoadAssignment, wg *completion.WaitGroup, callback func(error)) xds.AckingResourceMutatorRevertFunc { 893 s.mutex.Lock() 894 defer s.mutex.Unlock() 895 // 'callback' is not called if there is no change and this configuration has already been acked. 896 return s.endpointMutator.Upsert(EndpointTypeURL, name, conf, []string{"127.0.0.1"}, wg, callback) 897 } 898 899 // deleteEndpoint deletes an EDS endpoint. 900 func (s *xdsServer) deleteEndpoint(name string, wg *completion.WaitGroup, callback func(error)) xds.AckingResourceMutatorRevertFunc { 901 s.mutex.Lock() 902 defer s.mutex.Unlock() 903 // 'callback' is not called if there is no change and this configuration has already been acked. 904 return s.endpointMutator.Delete(EndpointTypeURL, name, []string{"127.0.0.1"}, wg, callback) 905 } 906 907 // upsertSecret either updates an existing SDS secret with 'name', or creates a new one. 908 func (s *xdsServer) upsertSecret(name string, conf *envoy_config_tls.Secret, wg *completion.WaitGroup, callback func(error)) xds.AckingResourceMutatorRevertFunc { 909 s.mutex.Lock() 910 defer s.mutex.Unlock() 911 // 'callback' is not called if there is no change and this configuration has already been acked. 912 return s.secretMutator.Upsert(SecretTypeURL, name, conf, []string{"127.0.0.1"}, wg, callback) 913 } 914 915 // deleteSecret deletes an SDS secret. 916 func (s *xdsServer) deleteSecret(name string, wg *completion.WaitGroup, callback func(error)) xds.AckingResourceMutatorRevertFunc { 917 s.mutex.Lock() 918 defer s.mutex.Unlock() 919 // 'callback' is not called if there is no change and this configuration has already been acked. 920 return s.secretMutator.Delete(SecretTypeURL, name, []string{"127.0.0.1"}, wg, callback) 921 } 922 923 func getListenerFilter(isIngress bool, useOriginalSourceAddr bool, proxyPort uint16) *envoy_config_listener.ListenerFilter { 924 conf := &cilium.BpfMetadata{ 925 IsIngress: isIngress, 926 UseOriginalSourceAddress: useOriginalSourceAddr, 927 BpfRoot: bpf.BPFFSRoot(), 928 IsL7Lb: false, 929 ProxyId: uint32(proxyPort), 930 } 931 932 return &envoy_config_listener.ListenerFilter{ 933 Name: "cilium.bpf_metadata", 934 ConfigType: &envoy_config_listener.ListenerFilter_TypedConfig{ 935 TypedConfig: toAny(conf), 936 }, 937 } 938 } 939 940 func (s *xdsServer) getListenerConf(name string, kind policy.L7ParserType, port uint16, isIngress bool, mayUseOriginalSourceAddr bool) *envoy_config_listener.Listener { 941 clusterName := egressClusterName 942 tlsClusterName := egressTLSClusterName 943 944 if isIngress { 945 clusterName = ingressClusterName 946 tlsClusterName = ingressTLSClusterName 947 } 948 949 addr, additionalAddr := GetLocalListenerAddresses(port, option.Config.IPv4Enabled(), option.Config.IPv6Enabled()) 950 listenerConf := &envoy_config_listener.Listener{ 951 Name: name, 952 Address: addr, 953 AdditionalAddresses: additionalAddr, 954 // FilterChains: []*envoy_config_listener.FilterChain 955 ListenerFilters: []*envoy_config_listener.ListenerFilter{ 956 // Always insert tls_inspector as the first filter 957 { 958 Name: "envoy.filters.listener.tls_inspector", 959 ConfigType: &envoy_config_listener.ListenerFilter_TypedConfig{ 960 TypedConfig: toAny(&envoy_extensions_listener_tls_inspector_v3.TlsInspector{}), 961 }, 962 }, 963 getListenerFilter(isIngress, mayUseOriginalSourceAddr, port), 964 }, 965 } 966 967 // Add filter chains 968 if kind == policy.ParserTypeHTTP { 969 listenerConf.FilterChains = append(listenerConf.FilterChains, s.getHttpFilterChainProto(clusterName, false, isIngress)) 970 971 // Add a TLS variant 972 listenerConf.FilterChains = append(listenerConf.FilterChains, s.getHttpFilterChainProto(tlsClusterName, true, isIngress)) 973 } else { 974 // Default TCP chain, takes care of all parsers in proxylib 975 listenerConf.FilterChains = append(listenerConf.FilterChains, s.getTcpFilterChainProto(clusterName, "", nil, false)) 976 977 // Add a TLS variant 978 listenerConf.FilterChains = append(listenerConf.FilterChains, s.getTcpFilterChainProto(tlsClusterName, "", nil, true)) 979 980 // Experimental TCP chain for MySQL 5.x 981 listenerConf.FilterChains = append(listenerConf.FilterChains, s.getTcpFilterChainProto(clusterName, 982 "envoy.filters.network.mysql_proxy", toAny(&envoy_mysql_proxy.MySQLProxy{ 983 StatPrefix: "mysql", 984 }), false)) 985 986 // Experimental TCP chain for MongoDB 987 listenerConf.FilterChains = append(listenerConf.FilterChains, s.getTcpFilterChainProto(clusterName, 988 "envoy.filters.network.mongo_proxy", toAny(&envoy_mongo_proxy.MongoProxy{ 989 StatPrefix: "mongo", 990 EmitDynamicMetadata: true, 991 }), false)) 992 } 993 return listenerConf 994 } 995 996 func (s *xdsServer) AddListener(name string, kind policy.L7ParserType, port uint16, isIngress bool, mayUseOriginalSourceAddr bool, wg *completion.WaitGroup) { 997 log.Debugf("Envoy: %s AddListener %s (mayUseOriginalSourceAddr: %v)", kind, name, mayUseOriginalSourceAddr) 998 999 s.addListener(name, func() *envoy_config_listener.Listener { 1000 return s.getListenerConf(name, kind, port, isIngress, mayUseOriginalSourceAddr) 1001 }, wg, nil, true) 1002 } 1003 1004 func (s *xdsServer) RemoveListener(name string, wg *completion.WaitGroup) xds.AckingResourceMutatorRevertFunc { 1005 return s.removeListener(name, wg, true) 1006 } 1007 1008 // removeListener removes an existing Envoy Listener. 1009 func (s *xdsServer) removeListener(name string, wg *completion.WaitGroup, isProxyListener bool) xds.AckingResourceMutatorRevertFunc { 1010 log.Debugf("Envoy: RemoveListener %s", name) 1011 1012 var listenerRevertFunc xds.AckingResourceMutatorRevertFunc 1013 1014 s.mutex.Lock() 1015 listener, ok := s.listeners[name] 1016 if ok && listener != nil { 1017 listener.count-- 1018 if listener.count == 0 { 1019 if isProxyListener { 1020 s.proxyListeners-- 1021 } 1022 delete(s.listeners, name) 1023 listenerRevertFunc = s.listenerMutator.Delete(ListenerTypeURL, name, []string{"127.0.0.1"}, wg, nil) 1024 } 1025 } else { 1026 // Bail out if this listener does not exist 1027 log.Fatalf("Envoy: Attempt to remove non-existent listener: %s", name) 1028 } 1029 s.mutex.Unlock() 1030 1031 return func(completion *completion.Completion) { 1032 s.mutex.Lock() 1033 if listenerRevertFunc != nil { 1034 listenerRevertFunc(completion) 1035 if isProxyListener { 1036 s.proxyListeners++ 1037 } 1038 } 1039 listener.count++ 1040 s.listeners[name] = listener 1041 s.mutex.Unlock() 1042 } 1043 } 1044 1045 func getL7Rules(l7Rules []api.PortRuleL7, l7Proto string) *cilium.L7NetworkPolicyRules { 1046 allowRules := make([]*cilium.L7NetworkPolicyRule, 0, len(l7Rules)) 1047 denyRules := make([]*cilium.L7NetworkPolicyRule, 0, len(l7Rules)) 1048 useEnvoyMetadataMatcher := false 1049 if strings.HasPrefix(l7Proto, "envoy.") { 1050 useEnvoyMetadataMatcher = true 1051 } 1052 for _, l7 := range l7Rules { 1053 if useEnvoyMetadataMatcher { 1054 envoyFilterName := l7Proto 1055 rule := &cilium.L7NetworkPolicyRule{MetadataRule: make([]*envoy_type_matcher.MetadataMatcher, 0, len(l7))} 1056 denyRule := false 1057 for k, v := range l7 { 1058 switch k { 1059 case "action": 1060 switch v { 1061 case "deny": 1062 denyRule = true 1063 } 1064 default: 1065 // map key to path segments and value to value matcher 1066 // For now only one path segment is allowed 1067 segments := strings.Split(k, "/") 1068 var path []*envoy_type_matcher.MetadataMatcher_PathSegment 1069 for _, key := range segments { 1070 path = append(path, &envoy_type_matcher.MetadataMatcher_PathSegment{ 1071 Segment: &envoy_type_matcher.MetadataMatcher_PathSegment_Key{Key: key}, 1072 }) 1073 } 1074 var value *envoy_type_matcher.ValueMatcher 1075 if len(v) == 0 { 1076 value = &envoy_type_matcher.ValueMatcher{ 1077 MatchPattern: &envoy_type_matcher.ValueMatcher_PresentMatch{ 1078 PresentMatch: true, 1079 }, 1080 } 1081 } else { 1082 value = &envoy_type_matcher.ValueMatcher{ 1083 MatchPattern: &envoy_type_matcher.ValueMatcher_ListMatch{ 1084 ListMatch: &envoy_type_matcher.ListMatcher{ 1085 MatchPattern: &envoy_type_matcher.ListMatcher_OneOf{ 1086 OneOf: &envoy_type_matcher.ValueMatcher{ 1087 MatchPattern: &envoy_type_matcher.ValueMatcher_StringMatch{ 1088 StringMatch: &envoy_type_matcher.StringMatcher{ 1089 MatchPattern: &envoy_type_matcher.StringMatcher_Exact{ 1090 Exact: v, 1091 }, 1092 IgnoreCase: false, 1093 }, 1094 }, 1095 }, 1096 }, 1097 }, 1098 }, 1099 } 1100 } 1101 rule.MetadataRule = append(rule.MetadataRule, &envoy_type_matcher.MetadataMatcher{ 1102 Filter: envoyFilterName, 1103 Path: path, 1104 Value: value, 1105 }) 1106 } 1107 } 1108 if denyRule { 1109 denyRules = append(denyRules, rule) 1110 } else { 1111 allowRules = append(allowRules, rule) 1112 } 1113 } else { 1114 // proxylib go extension key/value policy 1115 rule := &cilium.L7NetworkPolicyRule{Rule: make(map[string]string, len(l7))} 1116 for k, v := range l7 { 1117 rule.Rule[k] = v 1118 } 1119 allowRules = append(allowRules, rule) 1120 } 1121 } 1122 1123 rules := &cilium.L7NetworkPolicyRules{} 1124 if len(allowRules) > 0 { 1125 rules.L7AllowRules = allowRules 1126 } 1127 if len(denyRules) > 0 { 1128 rules.L7DenyRules = denyRules 1129 } 1130 return rules 1131 } 1132 1133 func getKafkaL7Rules(l7Rules []kafka.PortRule) *cilium.KafkaNetworkPolicyRules { 1134 allowRules := make([]*cilium.KafkaNetworkPolicyRule, 0, len(l7Rules)) 1135 for _, kr := range l7Rules { 1136 rule := &cilium.KafkaNetworkPolicyRule{ 1137 ApiVersion: kr.GetAPIVersion(), 1138 ApiKeys: kr.GetAPIKeys(), 1139 ClientId: kr.ClientID, 1140 Topic: kr.Topic, 1141 } 1142 allowRules = append(allowRules, rule) 1143 } 1144 1145 rules := &cilium.KafkaNetworkPolicyRules{} 1146 if len(allowRules) > 0 { 1147 rules.KafkaRules = allowRules 1148 } 1149 return rules 1150 } 1151 1152 func getSecretString(secretManager certificatemanager.SecretManager, hdr *api.HeaderMatch, ns string) (string, error) { 1153 value := "" 1154 var err error 1155 if hdr.Secret != nil { 1156 if secretManager == nil { 1157 err = fmt.Errorf("HeaderMatches: Nil secretManager") 1158 } else { 1159 value, err = secretManager.GetSecretString(context.TODO(), hdr.Secret, ns) 1160 } 1161 } 1162 // Only use Value if secret was not obtained 1163 if value == "" && hdr.Value != "" { 1164 value = hdr.Value 1165 if err != nil { 1166 log.WithError(err).Debug("HeaderMatches: Using a default value due to k8s secret not being available") 1167 err = nil 1168 } 1169 } 1170 1171 return value, err 1172 } 1173 1174 func getHTTPRule(secretManager certificatemanager.SecretManager, h *api.PortRuleHTTP, ns string) (*cilium.HttpNetworkPolicyRule, bool) { 1175 // Count the number of header matches we need 1176 cnt := len(h.Headers) + len(h.HeaderMatches) 1177 if h.Path != "" { 1178 cnt++ 1179 } 1180 if h.Method != "" { 1181 cnt++ 1182 } 1183 if h.Host != "" { 1184 cnt++ 1185 } 1186 1187 headers := make([]*envoy_config_route.HeaderMatcher, 0, cnt) 1188 if h.Path != "" { 1189 headers = append(headers, &envoy_config_route.HeaderMatcher{ 1190 Name: ":path", 1191 HeaderMatchSpecifier: &envoy_config_route.HeaderMatcher_StringMatch{ 1192 StringMatch: &envoy_type_matcher.StringMatcher{ 1193 MatchPattern: &envoy_type_matcher.StringMatcher_SafeRegex{ 1194 SafeRegex: &envoy_type_matcher.RegexMatcher{ 1195 Regex: h.Path, 1196 }, 1197 }, 1198 }, 1199 }, 1200 }) 1201 } 1202 if h.Method != "" { 1203 headers = append(headers, &envoy_config_route.HeaderMatcher{ 1204 Name: ":method", 1205 HeaderMatchSpecifier: &envoy_config_route.HeaderMatcher_StringMatch{ 1206 StringMatch: &envoy_type_matcher.StringMatcher{ 1207 MatchPattern: &envoy_type_matcher.StringMatcher_SafeRegex{ 1208 SafeRegex: &envoy_type_matcher.RegexMatcher{ 1209 Regex: h.Method, 1210 }, 1211 }, 1212 }, 1213 }, 1214 }) 1215 } 1216 if h.Host != "" { 1217 headers = append(headers, &envoy_config_route.HeaderMatcher{ 1218 Name: ":authority", 1219 HeaderMatchSpecifier: &envoy_config_route.HeaderMatcher_StringMatch{ 1220 StringMatch: &envoy_type_matcher.StringMatcher{ 1221 MatchPattern: &envoy_type_matcher.StringMatcher_SafeRegex{ 1222 SafeRegex: &envoy_type_matcher.RegexMatcher{ 1223 Regex: h.Host, 1224 }, 1225 }, 1226 }, 1227 }, 1228 }) 1229 } 1230 for _, hdr := range h.Headers { 1231 strs := strings.SplitN(hdr, " ", 2) 1232 if len(strs) == 2 { 1233 // Remove ':' in "X-Key: true" 1234 key := strings.TrimRight(strs[0], ":") 1235 // Header presence and matching (literal) value needed. 1236 headers = append(headers, &envoy_config_route.HeaderMatcher{ 1237 Name: key, 1238 HeaderMatchSpecifier: &envoy_config_route.HeaderMatcher_StringMatch{ 1239 StringMatch: &envoy_type_matcher.StringMatcher{ 1240 MatchPattern: &envoy_type_matcher.StringMatcher_Exact{ 1241 Exact: strs[1], 1242 }, 1243 }, 1244 }, 1245 }) 1246 } else { 1247 // Only header presence needed 1248 headers = append(headers, &envoy_config_route.HeaderMatcher{ 1249 Name: strs[0], 1250 HeaderMatchSpecifier: &envoy_config_route.HeaderMatcher_PresentMatch{PresentMatch: true}, 1251 }) 1252 } 1253 } 1254 1255 headerMatches := make([]*cilium.HeaderMatch, 0, len(h.HeaderMatches)) 1256 for _, hdr := range h.HeaderMatches { 1257 var mismatch_action cilium.HeaderMatch_MismatchAction 1258 switch hdr.Mismatch { 1259 case api.MismatchActionLog: 1260 mismatch_action = cilium.HeaderMatch_CONTINUE_ON_MISMATCH 1261 case api.MismatchActionAdd: 1262 mismatch_action = cilium.HeaderMatch_ADD_ON_MISMATCH 1263 case api.MismatchActionDelete: 1264 mismatch_action = cilium.HeaderMatch_DELETE_ON_MISMATCH 1265 case api.MismatchActionReplace: 1266 mismatch_action = cilium.HeaderMatch_REPLACE_ON_MISMATCH 1267 default: 1268 mismatch_action = cilium.HeaderMatch_FAIL_ON_MISMATCH 1269 } 1270 // Fetch the secret 1271 value, err := getSecretString(secretManager, hdr, ns) 1272 if err != nil { 1273 log.WithError(err).Warning("Failed fetching K8s Secret, header match will fail") 1274 // Envoy treats an empty exact match value as matching ANY value; adding 1275 // InvertMatch: true here will cause this rule to NEVER match. 1276 headers = append(headers, &envoy_config_route.HeaderMatcher{ 1277 Name: hdr.Name, 1278 HeaderMatchSpecifier: &envoy_config_route.HeaderMatcher_StringMatch{ 1279 StringMatch: &envoy_type_matcher.StringMatcher{ 1280 MatchPattern: &envoy_type_matcher.StringMatcher_Exact{ 1281 Exact: "", 1282 }, 1283 }, 1284 }, 1285 InvertMatch: true, 1286 }) 1287 } else { 1288 // Header presence and matching (literal) value needed. 1289 if mismatch_action == cilium.HeaderMatch_FAIL_ON_MISMATCH { 1290 if value != "" { 1291 headers = append(headers, &envoy_config_route.HeaderMatcher{ 1292 Name: hdr.Name, 1293 HeaderMatchSpecifier: &envoy_config_route.HeaderMatcher_StringMatch{ 1294 StringMatch: &envoy_type_matcher.StringMatcher{ 1295 MatchPattern: &envoy_type_matcher.StringMatcher_Exact{ 1296 Exact: value, 1297 }, 1298 }, 1299 }, 1300 }) 1301 } else { 1302 // Only header presence needed 1303 headers = append(headers, &envoy_config_route.HeaderMatcher{ 1304 Name: hdr.Name, 1305 HeaderMatchSpecifier: &envoy_config_route.HeaderMatcher_PresentMatch{PresentMatch: true}, 1306 }) 1307 } 1308 } else { 1309 log.Debugf("HeaderMatches: Adding %s", hdr.Name) 1310 headerMatches = append(headerMatches, &cilium.HeaderMatch{ 1311 MismatchAction: mismatch_action, 1312 Name: hdr.Name, 1313 Value: value, 1314 }) 1315 } 1316 } 1317 } 1318 if len(headers) == 0 { 1319 headers = nil 1320 } else { 1321 SortHeaderMatchers(headers) 1322 } 1323 if len(headerMatches) == 0 { 1324 headerMatches = nil 1325 } else { 1326 // Optimally we should sort the headerMatches to avoid 1327 // updating the policy if only the order of the rules 1328 // has changed. Right now, when 'headerMatches' is a 1329 // slice (rather than a map) the order only changes if 1330 // the order of the rules in the imported policies 1331 // changes, so there is minimal likelihood of 1332 // unnecessary policy updates. 1333 1334 // SortHeaderMatches(headerMatches) 1335 } 1336 1337 return &cilium.HttpNetworkPolicyRule{Headers: headers, HeaderMatches: headerMatches}, len(headerMatches) == 0 1338 } 1339 1340 var CiliumXDSConfigSource = &envoy_config_core.ConfigSource{ 1341 ResourceApiVersion: envoy_config_core.ApiVersion_V3, 1342 ConfigSourceSpecifier: &envoy_config_core.ConfigSource_ApiConfigSource{ 1343 ApiConfigSource: &envoy_config_core.ApiConfigSource{ 1344 ApiType: envoy_config_core.ApiConfigSource_GRPC, 1345 TransportApiVersion: envoy_config_core.ApiVersion_V3, 1346 SetNodeOnFirstMessageOnly: true, 1347 GrpcServices: []*envoy_config_core.GrpcService{ 1348 { 1349 TargetSpecifier: &envoy_config_core.GrpcService_EnvoyGrpc_{ 1350 EnvoyGrpc: &envoy_config_core.GrpcService_EnvoyGrpc{ 1351 ClusterName: CiliumXDSClusterName, 1352 }, 1353 }, 1354 }, 1355 }, 1356 }, 1357 }, 1358 } 1359 1360 // toEnvoyFullTLSContext converts a "policy" TLS context (i.e., from a CiliumNetworkPolicy or 1361 // CiliumClusterwideNetworkPolicy) into a "cilium envoy" TLS context (i.e., for the Cilium proxy plugin for Envoy). 1362 // 1363 // Deprecated: This includes both the CA *and* the cert/key. You almost certainly don't want this. Use 1364 // toEnvoyOriginatingTLSContext or toEnvoyTerminatingTLSContext instead. 1365 // x-ref: https://github.com/cilium/cilium/issues/31761 1366 func toEnvoyFullTLSContext(tls *policy.TLSContext) *cilium.TLSContext { 1367 return &cilium.TLSContext{ 1368 TrustedCa: tls.TrustedCA, 1369 CertificateChain: tls.CertificateChain, 1370 PrivateKey: tls.PrivateKey, 1371 } 1372 } 1373 1374 // toEnvoyOriginatingTLSContext converts a "policy" TLS context (i.e., from a CiliumNetworkPolicy or 1375 // CiliumClusterwideNetworkPolicy) for originating TLS (i.e., verifying TLS connections from *outside*) into a "cilium 1376 // envoy" TLS context (i.e., for the Cilium proxy plugin for Envoy). 1377 func toEnvoyOriginatingTLSContext(tls *policy.TLSContext) *cilium.TLSContext { 1378 return &cilium.TLSContext{ 1379 TrustedCa: tls.TrustedCA, 1380 } 1381 } 1382 1383 // toEnvoyTerminatingTLSContext converts a "policy" TLS context (i.e., from a CiliumNetworkPolicy or 1384 // CiliumClusterwideNetworkPolicy) for terminating TLS (i.e., providing a valid cert to clients *inside*) into a "cilium 1385 // envoy" TLS context (i.e., for the Cilium proxy plugin for Envoy). 1386 func toEnvoyTerminatingTLSContext(tls *policy.TLSContext) *cilium.TLSContext { 1387 return &cilium.TLSContext{ 1388 CertificateChain: tls.CertificateChain, 1389 PrivateKey: tls.PrivateKey, 1390 } 1391 } 1392 1393 func GetEnvoyHTTPRules(secretManager certificatemanager.SecretManager, l7Rules *api.L7Rules, ns string) (*cilium.HttpNetworkPolicyRules, bool) { 1394 if len(l7Rules.HTTP) > 0 { // Just cautious. This should never be false. 1395 // Assume none of the rules have side-effects so that rule evaluation can 1396 // be stopped as soon as the first allowing rule is found. 'canShortCircuit' 1397 // is set to 'false' below if any rules with side effects are encountered, 1398 // causing all the applicable rules to be evaluated instead. 1399 canShortCircuit := true 1400 httpRules := make([]*cilium.HttpNetworkPolicyRule, 0, len(l7Rules.HTTP)) 1401 for _, l7 := range l7Rules.HTTP { 1402 var cs bool 1403 rule, cs := getHTTPRule(secretManager, &l7, ns) 1404 httpRules = append(httpRules, rule) 1405 if !cs { 1406 canShortCircuit = false 1407 } 1408 } 1409 SortHTTPNetworkPolicyRules(httpRules) 1410 return &cilium.HttpNetworkPolicyRules{ 1411 HttpRules: httpRules, 1412 }, canShortCircuit 1413 } 1414 return nil, true 1415 } 1416 1417 func getPortNetworkPolicyRule(sel policy.CachedSelector, wildcard bool, l7Parser policy.L7ParserType, l7Rules *policy.PerSelectorPolicy, useFullTLSContext bool) (*cilium.PortNetworkPolicyRule, bool) { 1418 r := &cilium.PortNetworkPolicyRule{} 1419 1420 // Optimize the policy if the endpoint selector is a wildcard by 1421 // keeping remote policies list empty to match all remote policies. 1422 if !wildcard { 1423 selections := sel.GetSelections() 1424 1425 // No remote policies would match this rule. Discard it. 1426 if len(selections) == 0 { 1427 return nil, true 1428 } 1429 1430 r.RemotePolicies = selections.AsUint32Slice() 1431 } 1432 1433 if l7Rules == nil { 1434 // L3/L4 only rule, everything in L7 is allowed && no TLS 1435 return r, true 1436 } 1437 1438 if l7Rules.IsDeny { 1439 r.Deny = true 1440 return r, false 1441 } 1442 1443 if useFullTLSContext { 1444 // Intentionally retain compatibility with older, buggy behaviour. In this mode the full contents of the 1445 // Kubernetes secret identified by TLS context is copied into the downstream/upstream TLS context. For the 1446 // downstream (i.e., in-cluster pod) this could include a CA bundle if a ca.crt key is present in the secret, 1447 // which may incorrectly lead Envoy to require client certificates. 1448 if l7Rules.TerminatingTLS != nil { 1449 r.DownstreamTlsContext = toEnvoyFullTLSContext(l7Rules.TerminatingTLS) 1450 } 1451 if l7Rules.OriginatingTLS != nil { 1452 r.UpstreamTlsContext = toEnvoyFullTLSContext(l7Rules.OriginatingTLS) 1453 } 1454 } else { 1455 if l7Rules.TerminatingTLS != nil { 1456 r.DownstreamTlsContext = toEnvoyTerminatingTLSContext(l7Rules.TerminatingTLS) 1457 } 1458 if l7Rules.OriginatingTLS != nil { 1459 r.UpstreamTlsContext = toEnvoyOriginatingTLSContext(l7Rules.OriginatingTLS) 1460 } 1461 } 1462 if len(l7Rules.ServerNames) > 0 { 1463 r.ServerNames = make([]string, 0, len(l7Rules.ServerNames)) 1464 for sni := range l7Rules.ServerNames { 1465 r.ServerNames = append(r.ServerNames, sni) 1466 } 1467 sort.Strings(r.ServerNames) 1468 } 1469 1470 // Assume none of the rules have side-effects so that rule evaluation can 1471 // be stopped as soon as the first allowing rule is found. 'canShortCircuit' 1472 // is set to 'false' below if any rules with side effects are encountered, 1473 // causing all the applicable rules to be evaluated instead. 1474 canShortCircuit := true 1475 switch l7Parser { 1476 case policy.ParserTypeHTTP: 1477 // 'r.L7' is an interface which must not be set to a typed 'nil', 1478 // so check if we have any rules 1479 if len(l7Rules.HTTP) > 0 { 1480 // Use L7 rules computed earlier? 1481 var httpRules *cilium.HttpNetworkPolicyRules 1482 if l7Rules.EnvoyHTTPRules != nil { 1483 httpRules = l7Rules.EnvoyHTTPRules 1484 canShortCircuit = l7Rules.CanShortCircuit 1485 } else { 1486 httpRules, canShortCircuit = GetEnvoyHTTPRules(nil, &l7Rules.L7Rules, "") 1487 } 1488 r.L7 = &cilium.PortNetworkPolicyRule_HttpRules{ 1489 HttpRules: httpRules, 1490 } 1491 } 1492 1493 case policy.ParserTypeKafka: 1494 // Kafka is implemented as an Envoy Go Extension 1495 if len(l7Rules.Kafka) > 0 { 1496 // L7 rules are not sorted 1497 r.L7Proto = l7Parser.String() 1498 r.L7 = &cilium.PortNetworkPolicyRule_KafkaRules{ 1499 KafkaRules: getKafkaL7Rules(l7Rules.Kafka), 1500 } 1501 } 1502 1503 case policy.ParserTypeDNS: 1504 // TODO: Support DNS. For now, just ignore any DNS L7 rule. 1505 1506 default: 1507 // Assume unknown parser types use a Key-Value Pair policy 1508 if len(l7Rules.L7) > 0 { 1509 // L7 rules are not sorted 1510 r.L7Proto = l7Parser.String() 1511 r.L7 = &cilium.PortNetworkPolicyRule_L7Rules{ 1512 L7Rules: getL7Rules(l7Rules.L7, r.L7Proto), 1513 } 1514 } 1515 } 1516 1517 return r, canShortCircuit 1518 } 1519 1520 // getWildcardNetworkPolicyRule returns the rule for port 0, which 1521 // will be considered after port-specific rules. 1522 func getWildcardNetworkPolicyRule(selectors policy.L7DataMap) *cilium.PortNetworkPolicyRule { 1523 // selections are pre-sorted, so sorting is only needed if merging selections from multiple selectors 1524 if len(selectors) == 1 { 1525 for sel := range selectors { 1526 if sel.IsWildcard() { 1527 return &cilium.PortNetworkPolicyRule{} 1528 } 1529 selections := sel.GetSelections() 1530 if len(selections) == 0 { 1531 // No remote policies would match this rule. Discard it. 1532 return nil 1533 } 1534 return &cilium.PortNetworkPolicyRule{ 1535 RemotePolicies: selections.AsUint32Slice(), 1536 } 1537 } 1538 } 1539 1540 // Get selections for each selector and count how many there are 1541 sels := make([][]uint32, 0, len(selectors)) 1542 wildcardFound := false 1543 var count int 1544 for sel, l7 := range selectors { 1545 if sel.IsWildcard() { 1546 wildcardFound = true 1547 break 1548 } 1549 1550 if l7.IsRedirect() { 1551 // Issue a warning if this port-0 rule is a redirect. 1552 // Deny rules don't support L7 therefore for the deny case 1553 // l7.IsRedirect() will always return false. 1554 log.Warningf("L3-only rule for selector %v surprisingly requires proxy redirection (%v)!", sel, *l7) 1555 } 1556 1557 selections := sel.GetSelections() 1558 if len(selections) == 0 { 1559 continue 1560 } 1561 count += len(selections) 1562 sels = append(sels, selections.AsUint32Slice()) 1563 } 1564 1565 var remotePolicies []uint32 1566 1567 if wildcardFound { 1568 // Optimize the policy if the endpoint selector is a wildcard by 1569 // keeping remote policies list empty to match all remote policies. 1570 } else if count == 0 { 1571 // No remote policies would match this rule. Discard it. 1572 return nil 1573 } else { 1574 // allocate slice and copy selected identities 1575 remotePolicies = make([]uint32, 0, count) 1576 for _, selections := range sels { 1577 remotePolicies = append(remotePolicies, selections...) 1578 } 1579 slices.Sort(remotePolicies) 1580 remotePolicies = slices.Compact(remotePolicies) 1581 } 1582 return &cilium.PortNetworkPolicyRule{ 1583 RemotePolicies: remotePolicies, 1584 } 1585 } 1586 1587 func getDirectionNetworkPolicy(ep endpoint.EndpointUpdater, l4Policy policy.L4PolicyMap, policyEnforced bool, useFullTLSContext bool, vis policy.DirectionalVisibilityPolicy, dir string) []*cilium.PortNetworkPolicy { 1588 // TODO: integrate visibility with enforced policy 1589 if !policyEnforced { 1590 PerPortPolicies := make([]*cilium.PortNetworkPolicy, 0, len(vis)+1) 1591 // Always allow all ports 1592 PerPortPolicies = append(PerPortPolicies, allowAllTCPPortNetworkPolicy) 1593 for _, visMeta := range vis { 1594 // Set up rule with 'L7Proto' as needed for proxylib parsers 1595 if visMeta.Proto == u8proto.TCP && visMeta.Parser != policy.ParserTypeHTTP && visMeta.Parser != policy.ParserTypeDNS { 1596 PerPortPolicies = append(PerPortPolicies, &cilium.PortNetworkPolicy{ 1597 Port: uint32(visMeta.Port), 1598 Protocol: envoy_config_core.SocketAddress_TCP, 1599 Rules: []*cilium.PortNetworkPolicyRule{ 1600 { 1601 L7Proto: visMeta.Parser.String(), 1602 }, 1603 }, 1604 }) 1605 } 1606 } 1607 return SortPortNetworkPolicies(PerPortPolicies) 1608 } 1609 1610 if l4Policy == nil || l4Policy.Len() == 0 { 1611 return nil 1612 } 1613 1614 PerPortPolicies := make([]*cilium.PortNetworkPolicy, 0, l4Policy.Len()) 1615 l4Policy.ForEach(func(l4 *policy.L4Filter) bool { 1616 var protocol envoy_config_core.SocketAddress_Protocol 1617 switch l4.Protocol { 1618 case api.ProtoTCP: 1619 protocol = envoy_config_core.SocketAddress_TCP 1620 case api.ProtoUDP, api.ProtoSCTP: 1621 // UDP/SCTP rules not sent to Envoy for now. 1622 return true 1623 } 1624 1625 port := l4.Port 1626 if port == 0 && l4.PortName != "" { 1627 port = ep.GetNamedPort(l4.Ingress, l4.PortName, uint8(l4.U8Proto)) 1628 if port == 0 { 1629 return true // Skip if a named port can not be resolved (yet) 1630 } 1631 } 1632 1633 rules := make([]*cilium.PortNetworkPolicyRule, 0, len(l4.PerSelectorPolicies)) 1634 allowAll := false 1635 1636 // Assume none of the rules have side-effects so that rule evaluation can 1637 // be stopped as soon as the first allowing rule is found. 'canShortCircuit' 1638 // is set to 'false' below if any rules with side effects are encountered, 1639 // causing all the applicable rules to be evaluated instead. 1640 canShortCircuit := true 1641 1642 if port == 0 { 1643 // L3-only rule, must generate L7 allow-all in case there are other 1644 // port-specific rules. Otherwise traffic from allowed remotes could be dropped. 1645 rule := getWildcardNetworkPolicyRule(l4.PerSelectorPolicies) 1646 if rule != nil { 1647 log.WithFields(logrus.Fields{ 1648 logfields.EndpointID: ep.GetID(), 1649 logfields.TrafficDirection: dir, 1650 logfields.Port: port, 1651 logfields.PolicyID: rule.RemotePolicies, 1652 }).Debug("Wildcard PortNetworkPolicyRule matching remote IDs") 1653 1654 if len(rule.RemotePolicies) == 0 { 1655 // Got an allow-all rule, which can short-circuit all of 1656 // the other rules. 1657 allowAll = true 1658 } 1659 rules = append(rules, rule) 1660 } 1661 } else { 1662 nSelectors := len(l4.PerSelectorPolicies) 1663 for sel, l7 := range l4.PerSelectorPolicies { 1664 // A single selector is effectively a wildcard, as bpf passes through 1665 // only allowed l3. If there are multiple selectors for this l4-filter 1666 // then the proxy may need to drop some allowed l3 due to l7 rules potentially 1667 // being different between the selectors. 1668 wildcard := nSelectors == 1 || sel.IsWildcard() 1669 rule, cs := getPortNetworkPolicyRule(sel, wildcard, l4.L7Parser, l7, useFullTLSContext) 1670 if rule != nil { 1671 if !cs { 1672 canShortCircuit = false 1673 } 1674 1675 log.WithFields(logrus.Fields{ 1676 logfields.EndpointID: ep.GetID(), 1677 logfields.TrafficDirection: dir, 1678 logfields.Port: port, 1679 logfields.PolicyID: rule.RemotePolicies, 1680 logfields.ServerNames: rule.ServerNames, 1681 }).Debug("PortNetworkPolicyRule matching remote IDs") 1682 1683 if len(rule.RemotePolicies) == 0 && rule.L7 == nil && rule.DownstreamTlsContext == nil && rule.UpstreamTlsContext == nil && len(rule.ServerNames) == 0 { 1684 // Got an allow-all rule, which can short-circuit all of 1685 // the other rules. 1686 allowAll = true 1687 } 1688 rules = append(rules, rule) 1689 } 1690 } 1691 } 1692 // Short-circuit rules if a rule allows all and all other rules can be short-circuited 1693 if allowAll && canShortCircuit { 1694 log.Debug("Short circuiting HTTP rules due to rule allowing all and no other rules needing attention") 1695 rules = nil 1696 } 1697 1698 // No rule for this port matches any remote identity. 1699 // This means that no traffic was explicitly allowed for this port. 1700 // In this case, just don't generate any PortNetworkPolicy for this 1701 // port. 1702 if !allowAll && len(rules) == 0 { 1703 return true 1704 } 1705 1706 // NPDS supports port ranges. 1707 PerPortPolicies = append(PerPortPolicies, &cilium.PortNetworkPolicy{ 1708 Port: uint32(port), 1709 EndPort: uint32(l4.EndPort), 1710 Protocol: protocol, 1711 Rules: SortPortNetworkPolicyRules(rules), 1712 }) 1713 return true 1714 }) 1715 1716 if len(PerPortPolicies) == 0 { 1717 return nil 1718 } 1719 1720 return SortPortNetworkPolicies(PerPortPolicies) 1721 } 1722 1723 // getNetworkPolicy converts a network policy into a cilium.NetworkPolicy. 1724 func getNetworkPolicy(ep endpoint.EndpointUpdater, vis *policy.VisibilityPolicy, ips []string, l4Policy *policy.L4Policy, 1725 ingressPolicyEnforced, egressPolicyEnforced, useFullTLSContext bool, 1726 ) *cilium.NetworkPolicy { 1727 p := &cilium.NetworkPolicy{ 1728 EndpointIps: ips, 1729 EndpointId: ep.GetID(), 1730 ConntrackMapName: ep.ConntrackNameLocked(), 1731 } 1732 1733 var visIngress policy.DirectionalVisibilityPolicy 1734 var visEgress policy.DirectionalVisibilityPolicy 1735 if vis != nil { 1736 visIngress = vis.Ingress 1737 visEgress = vis.Egress 1738 } 1739 var ingressMap policy.L4PolicyMap 1740 var egressMap policy.L4PolicyMap 1741 if l4Policy != nil { 1742 ingressMap = l4Policy.Ingress.PortRules 1743 egressMap = l4Policy.Egress.PortRules 1744 } 1745 p.IngressPerPortPolicies = getDirectionNetworkPolicy(ep, ingressMap, ingressPolicyEnforced, useFullTLSContext, visIngress, "ingress") 1746 p.EgressPerPortPolicies = getDirectionNetworkPolicy(ep, egressMap, egressPolicyEnforced, useFullTLSContext, visEgress, "egress") 1747 1748 return p 1749 } 1750 1751 // return the Envoy proxy node IDs that need to ACK the policy. 1752 func getNodeIDs(ep endpoint.EndpointUpdater, policy *policy.L4Policy) []string { 1753 nodeIDs := make([]string, 0, 1) 1754 1755 // Host proxy uses "127.0.0.1" as the nodeID 1756 nodeIDs = append(nodeIDs, "127.0.0.1") 1757 // Require additional ACK from proxylib if policy has proxylib redirects 1758 // Note that if a previous policy had a proxylib redirect and this one does not, 1759 // we only wait for the ACK from the main Envoy node ID. 1760 if policy.HasProxylibRedirect() { 1761 // Proxylib uses "127.0.0.2" as the nodeID 1762 nodeIDs = append(nodeIDs, "127.0.0.2") 1763 } 1764 return nodeIDs 1765 } 1766 1767 func (s *xdsServer) UpdateNetworkPolicy(ep endpoint.EndpointUpdater, vis *policy.VisibilityPolicy, policy *policy.L4Policy, 1768 ingressPolicyEnforced, egressPolicyEnforced bool, wg *completion.WaitGroup, 1769 ) (error, func() error) { 1770 s.mutex.Lock() 1771 defer s.mutex.Unlock() 1772 1773 var ips []string 1774 if ipv6 := ep.GetIPv6Address(); ipv6 != "" { 1775 ips = append(ips, ipv6) 1776 } 1777 if ipv4 := ep.GetIPv4Address(); ipv4 != "" { 1778 ips = append(ips, ipv4) 1779 } 1780 if len(ips) == 0 { 1781 // It looks like the "host EP" (identity == 1) has no IPs, so it is possible to find 1782 // there are no IPs here. In this case just skip without updating a policy, as 1783 // policies are always keyed by an IP. 1784 // 1785 // TODO: When L7 policy support for the host is needed, all host IPs should be 1786 // considered here? 1787 log.WithField(logfields.EndpointID, ep.GetID()).Debug("Endpoint has no IP addresses") 1788 return nil, func() error { return nil } 1789 } 1790 1791 networkPolicy := getNetworkPolicy(ep, vis, ips, policy, ingressPolicyEnforced, egressPolicyEnforced, s.config.useFullTLSContext) 1792 1793 // First, validate the policy 1794 err := networkPolicy.Validate() 1795 if err != nil { 1796 return fmt.Errorf("error validating generated NetworkPolicy for Endpoint %d: %w", ep.GetID(), err), nil 1797 } 1798 1799 nodeIDs := getNodeIDs(ep, policy) 1800 1801 // If there are no listeners configured, the local node's Envoy proxy won't 1802 // query for network policies and therefore will never ACK them, and we'd 1803 // wait forever. 1804 if s.proxyListeners == 0 { 1805 wg = nil 1806 } 1807 1808 // When successful, push policy into the cache. 1809 var callback func(error) 1810 if policy != nil { 1811 policyRevision := policy.Revision 1812 callback = func(err error) { 1813 if err == nil { 1814 go ep.OnProxyPolicyUpdate(policyRevision) 1815 } 1816 } 1817 } 1818 epID := ep.GetID() 1819 resourceName := strconv.FormatUint(epID, 10) 1820 revertFunc := s.NetworkPolicyMutator.Upsert(NetworkPolicyTypeURL, resourceName, networkPolicy, nodeIDs, wg, callback) 1821 revertUpdatedNetworkPolicyEndpoints := make(map[string]endpoint.EndpointUpdater, len(ips)) 1822 for _, ip := range ips { 1823 revertUpdatedNetworkPolicyEndpoints[ip] = s.localEndpointStore.getLocalEndpoint(ip) 1824 s.localEndpointStore.setLocalEndpoint(ip, ep) 1825 } 1826 1827 return nil, func() error { 1828 log.Debug("Reverting xDS network policy update") 1829 1830 s.mutex.Lock() 1831 defer s.mutex.Unlock() 1832 1833 for ip, ep := range revertUpdatedNetworkPolicyEndpoints { 1834 if ep == nil { 1835 s.localEndpointStore.removeLocalEndpoint(ip) 1836 } else { 1837 s.localEndpointStore.setLocalEndpoint(ip, ep) 1838 } 1839 } 1840 1841 // Don't wait for an ACK for the reverted xDS updates. 1842 // This is best-effort. 1843 revertFunc(nil) 1844 1845 log.Debug("Finished reverting xDS network policy update") 1846 1847 return nil 1848 } 1849 } 1850 1851 func (s *xdsServer) RemoveNetworkPolicy(ep endpoint.EndpointInfoSource) { 1852 s.mutex.Lock() 1853 defer s.mutex.Unlock() 1854 1855 epID := ep.GetID() 1856 resourceName := strconv.FormatUint(epID, 10) 1857 s.networkPolicyCache.Delete(NetworkPolicyTypeURL, resourceName) 1858 1859 ip := ep.GetIPv6Address() 1860 if ip != "" { 1861 s.localEndpointStore.removeLocalEndpoint(ip) 1862 } 1863 ip = ep.GetIPv4Address() 1864 if ip != "" { 1865 s.localEndpointStore.removeLocalEndpoint(ip) 1866 // Delete node resources held in the cache for the endpoint 1867 s.NetworkPolicyMutator.DeleteNode(ip) 1868 } 1869 } 1870 1871 func (s *xdsServer) RemoveAllNetworkPolicies() { 1872 s.networkPolicyCache.Clear(NetworkPolicyTypeURL) 1873 } 1874 1875 func (s *xdsServer) GetNetworkPolicies(resourceNames []string) (map[string]*cilium.NetworkPolicy, error) { 1876 resources, err := s.networkPolicyCache.GetResources(NetworkPolicyTypeURL, 0, "", resourceNames) 1877 if err != nil { 1878 return nil, err 1879 } 1880 networkPolicies := make(map[string]*cilium.NetworkPolicy, len(resources.Resources)) 1881 for _, res := range resources.Resources { 1882 networkPolicy := res.(*cilium.NetworkPolicy) 1883 for _, ip := range networkPolicy.EndpointIps { 1884 networkPolicies[ip] = networkPolicy 1885 } 1886 } 1887 return networkPolicies, nil 1888 } 1889 1890 // Resources contains all Envoy resources parsed from a CiliumEnvoyConfig CRD 1891 type Resources struct { 1892 Listeners []*envoy_config_listener.Listener 1893 Secrets []*envoy_config_tls.Secret 1894 Routes []*envoy_config_route.RouteConfiguration 1895 Clusters []*envoy_config_cluster.Cluster 1896 Endpoints []*envoy_config_endpoint.ClusterLoadAssignment 1897 1898 // Callback functions that are called if the corresponding Listener change was successfully acked by Envoy 1899 PortAllocationCallbacks map[string]func(context.Context) error 1900 } 1901 1902 // ListenersAddedOrDeleted returns 'true' if a listener is added or removed when updating from 'old' 1903 // to 'new' 1904 func (old *Resources) ListenersAddedOrDeleted(new *Resources) bool { 1905 // Typically the number of listeners in a CEC is small (e.g, one), so it should be OK to 1906 // scan the slices like here 1907 for _, nl := range new.Listeners { 1908 found := false 1909 for _, ol := range old.Listeners { 1910 if ol.Name == nl.Name { 1911 found = true 1912 break 1913 } 1914 } 1915 if !found { 1916 return true // a listener was added 1917 } 1918 } 1919 for _, ol := range old.Listeners { 1920 found := false 1921 for _, nl := range new.Listeners { 1922 if nl.Name == ol.Name { 1923 found = true 1924 break 1925 } 1926 } 1927 if !found { 1928 return true // a listener was removed 1929 } 1930 } 1931 return false 1932 } 1933 1934 func (s *xdsServer) UpsertEnvoyResources(ctx context.Context, resources Resources) error { 1935 if option.Config.Debug { 1936 msg := "" 1937 sep := "" 1938 if len(resources.Listeners) > 0 { 1939 msg += fmt.Sprintf("%d listeners", len(resources.Listeners)) 1940 sep = ", " 1941 } 1942 if len(resources.Routes) > 0 { 1943 msg += fmt.Sprintf("%s%d routes", sep, len(resources.Routes)) 1944 sep = ", " 1945 } 1946 if len(resources.Clusters) > 0 { 1947 msg += fmt.Sprintf("%s%d clusters", sep, len(resources.Clusters)) 1948 sep = ", " 1949 } 1950 if len(resources.Endpoints) > 0 { 1951 msg += fmt.Sprintf("%s%d endpoints", sep, len(resources.Endpoints)) 1952 sep = ", " 1953 } 1954 if len(resources.Secrets) > 0 { 1955 msg += fmt.Sprintf("%s%d secrets", sep, len(resources.Secrets)) 1956 } 1957 1958 log.Debugf("UpsertEnvoyResources: Upserting %s...", msg) 1959 } 1960 var wg *completion.WaitGroup 1961 // Listener config may fail if it refers to a cluster that has not been added yet, so we 1962 // must wait for Envoy to ACK cluster config before adding Listeners to be sure Listener 1963 // config does not fail for this reason. 1964 // Enable wait before new Listeners are added if clusters are also added. 1965 if len(resources.Listeners) > 0 && len(resources.Clusters) > 0 { 1966 wg = completion.NewWaitGroup(ctx) 1967 } 1968 var revertFuncs xds.AckingResourceMutatorRevertFuncList 1969 // Do not wait for the addition of routes, clusters, endpoints, routes, 1970 // or secrets as there are no guarantees that these additions will be 1971 // acked. For example, if the listener referring to was already deleted 1972 // earlier, there are no references to the deleted resources anymore, 1973 // in which case we could wait forever for the ACKs. This could also 1974 // happen if there is no listener referring to these named 1975 // resources to begin with. 1976 // If both listeners and clusters are added then wait for clusters. 1977 for _, r := range resources.Secrets { 1978 log.Debugf("Envoy upsertSecret %s", r.Name) 1979 revertFuncs = append(revertFuncs, s.upsertSecret(r.Name, r, nil, nil)) 1980 } 1981 for _, r := range resources.Endpoints { 1982 log.Debugf("Envoy upsertEndpoint %s %v", r.ClusterName, r) 1983 revertFuncs = append(revertFuncs, s.upsertEndpoint(r.ClusterName, r, nil, nil)) 1984 } 1985 for _, r := range resources.Clusters { 1986 log.Debugf("Envoy upsertCluster %s %v", r.Name, r) 1987 revertFuncs = append(revertFuncs, s.upsertCluster(r.Name, r, wg, nil)) 1988 } 1989 for _, r := range resources.Routes { 1990 log.Debugf("Envoy upsertRoute %s %v", r.Name, r) 1991 revertFuncs = append(revertFuncs, s.upsertRoute(r.Name, r, nil, nil)) 1992 } 1993 // Wait before new Listeners are added if clusters were also added above. 1994 if wg != nil { 1995 start := time.Now() 1996 log.Debug("UpsertEnvoyResources: Waiting for cluster updates to complete...") 1997 err := wg.Wait() 1998 log.Debugf("UpsertEnvoyResources: Wait time for cluster updates %v (err: %s)", time.Since(start), err) 1999 2000 // revert all changes in case of failure 2001 if err != nil { 2002 revertFuncs.Revert(nil) 2003 log.Debug("UpsertEnvoyResources: Finished reverting failed xDS transactions") 2004 return err 2005 } 2006 wg = nil 2007 } 2008 // Wait only if new Listeners are added, as they will always be acked. 2009 // (unreferenced routes or endpoints (and maybe clusters) are not ACKed or NACKed). 2010 if len(resources.Listeners) > 0 { 2011 wg = completion.NewWaitGroup(ctx) 2012 } 2013 for _, r := range resources.Listeners { 2014 log.Debugf("Envoy upsertListener %s %v", r.Name, r) 2015 listenerName := r.Name 2016 revertFuncs = append(revertFuncs, s.upsertListener(r.Name, r, wg, 2017 // this callback is not called if there is no change 2018 func(err error) { 2019 if err == nil && resources.PortAllocationCallbacks[listenerName] != nil { 2020 if callbackErr := resources.PortAllocationCallbacks[listenerName](ctx); callbackErr != nil { 2021 log.WithError(callbackErr).Warn("Failure in port allocation callback") 2022 } 2023 } 2024 })) 2025 } 2026 if wg != nil { 2027 start := time.Now() 2028 log.Debug("UpsertEnvoyResources: Waiting for proxy updates to complete...") 2029 err := wg.Wait() 2030 log.Debugf("UpsertEnvoyResources: Wait time for proxy updates %v (err: %s)", time.Since(start), err) 2031 2032 // revert all changes in case of failure 2033 if err != nil { 2034 revertFuncs.Revert(nil) 2035 log.Debug("UpsertEnvoyResources: Finished reverting failed xDS transactions") 2036 } 2037 return err 2038 } 2039 return nil 2040 } 2041 2042 func (s *xdsServer) UpdateEnvoyResources(ctx context.Context, old, new Resources) error { 2043 waitForDelete := false 2044 var wg *completion.WaitGroup 2045 var revertFuncs xds.AckingResourceMutatorRevertFuncList 2046 // Wait only if new Listeners are added, as they will always be acked. 2047 // (unreferenced routes or endpoints (and maybe clusters) are not ACKed or NACKed). 2048 if len(new.Listeners) > 0 { 2049 wg = completion.NewWaitGroup(ctx) 2050 } 2051 // Delete old listeners not added in 'new' or if old and new listener have different ports 2052 var deleteListeners []*envoy_config_listener.Listener 2053 for _, oldListener := range old.Listeners { 2054 found := false 2055 port := uint32(0) 2056 if addr := oldListener.Address.GetSocketAddress(); addr != nil { 2057 port = addr.GetPortValue() 2058 } 2059 for _, newListener := range new.Listeners { 2060 if newListener.Name == oldListener.Name { 2061 if addr := newListener.Address.GetSocketAddress(); addr != nil && addr.GetPortValue() != port { 2062 log.Debugf("UpdateEnvoyResources: %s port changing from %d to %d...", newListener.Name, port, addr.GetPortValue()) 2063 waitForDelete = true 2064 } else { 2065 // port is not changing, remove from new.PortAllocations to prevent acking an already acked port. 2066 delete(new.PortAllocationCallbacks, newListener.Name) 2067 found = true 2068 } 2069 break 2070 } 2071 } 2072 if !found { 2073 deleteListeners = append(deleteListeners, oldListener) 2074 } 2075 } 2076 log.Debugf("UpdateEnvoyResources: Deleting %d, Upserting %d listeners...", len(deleteListeners), len(new.Listeners)) 2077 for _, listener := range deleteListeners { 2078 listenerName := listener.Name 2079 revertFuncs = append(revertFuncs, s.deleteListener(listener.Name, wg, 2080 func(err error) { 2081 if err == nil && old.PortAllocationCallbacks[listenerName] != nil { 2082 if callbackErr := old.PortAllocationCallbacks[listenerName](ctx); callbackErr != nil { 2083 log.WithError(callbackErr).Warn("Failure in port allocation callback") 2084 } 2085 } 2086 })) 2087 } 2088 2089 // Do not wait for the deletion of routes, clusters, endpoints, or 2090 // secrets as there are no quarantees that these deletions will be 2091 // acked. For example, if the listener referring to was already deleted 2092 // earlier, there are no references to the deleted resources any more, 2093 // in which case we could wait forever for the ACKs. This could also 2094 // happen if there is no listener referring to these other named 2095 // resources to begin with. 2096 2097 // Delete old routes not added in 'new' 2098 var deleteRoutes []*envoy_config_route.RouteConfiguration 2099 for _, oldRoute := range old.Routes { 2100 found := false 2101 for _, newRoute := range new.Routes { 2102 if newRoute.Name == oldRoute.Name { 2103 found = true 2104 } 2105 } 2106 if !found { 2107 deleteRoutes = append(deleteRoutes, oldRoute) 2108 } 2109 } 2110 log.Debugf("UpdateEnvoyResources: Deleting %d, Upserting %d routes...", len(deleteRoutes), len(new.Routes)) 2111 for _, route := range deleteRoutes { 2112 revertFuncs = append(revertFuncs, s.deleteRoute(route.Name, nil, nil)) 2113 } 2114 2115 // Delete old clusters not added in 'new' 2116 var deleteClusters []*envoy_config_cluster.Cluster 2117 for _, oldCluster := range old.Clusters { 2118 found := false 2119 for _, newCluster := range new.Clusters { 2120 if newCluster.Name == oldCluster.Name { 2121 found = true 2122 } 2123 } 2124 if !found { 2125 deleteClusters = append(deleteClusters, oldCluster) 2126 } 2127 } 2128 log.Debugf("UpdateEnvoyResources: Deleting %d, Upserting %d clusters...", len(deleteClusters), len(new.Clusters)) 2129 for _, cluster := range deleteClusters { 2130 revertFuncs = append(revertFuncs, s.deleteCluster(cluster.Name, nil, nil)) 2131 } 2132 2133 // Delete old endpoints not added in 'new' 2134 var deleteEndpoints []*envoy_config_endpoint.ClusterLoadAssignment 2135 for _, oldEndpoint := range old.Endpoints { 2136 found := false 2137 for _, newEndpoint := range new.Endpoints { 2138 if newEndpoint.ClusterName == oldEndpoint.ClusterName { 2139 found = true 2140 } 2141 } 2142 if !found { 2143 deleteEndpoints = append(deleteEndpoints, oldEndpoint) 2144 } 2145 } 2146 log.Debugf("UpdateEnvoyResources: Deleting %d, Upserting %d endpoints...", len(deleteEndpoints), len(new.Endpoints)) 2147 for _, endpoint := range deleteEndpoints { 2148 revertFuncs = append(revertFuncs, s.deleteEndpoint(endpoint.ClusterName, nil, nil)) 2149 } 2150 2151 // Delete old secrets not added in 'new' 2152 var deleteSecrets []*envoy_config_tls.Secret 2153 for _, oldSecret := range old.Secrets { 2154 found := false 2155 for _, newSecret := range new.Secrets { 2156 if newSecret.Name == oldSecret.Name { 2157 found = true 2158 } 2159 } 2160 if !found { 2161 deleteSecrets = append(deleteSecrets, oldSecret) 2162 } 2163 } 2164 log.Debugf("UpdateEnvoyResources: Deleting %d, Upserting %d secrets...", len(deleteSecrets), len(new.Secrets)) 2165 for _, secret := range deleteSecrets { 2166 revertFuncs = append(revertFuncs, s.deleteSecret(secret.Name, nil, nil)) 2167 } 2168 2169 // Have to wait for deletes to complete before adding new listeners if a listener's port number is changed. 2170 if wg != nil && waitForDelete { 2171 start := time.Now() 2172 log.Debug("UpdateEnvoyResources: Waiting for proxy deletes to complete...") 2173 err := wg.Wait() 2174 if err != nil { 2175 log.Debug("UpdateEnvoyResources: delete failed: ", err) 2176 } 2177 log.Debug("UpdateEnvoyResources: Wait time for proxy deletes: ", time.Since(start)) 2178 // new wait group for adds 2179 wg = completion.NewWaitGroup(ctx) 2180 } 2181 2182 // Add new Secrets 2183 for _, r := range new.Secrets { 2184 revertFuncs = append(revertFuncs, s.upsertSecret(r.Name, r, nil, nil)) 2185 } 2186 // Add new Endpoints 2187 for _, r := range new.Endpoints { 2188 revertFuncs = append(revertFuncs, s.upsertEndpoint(r.ClusterName, r, nil, nil)) 2189 } 2190 // Add new Clusters 2191 for _, r := range new.Clusters { 2192 revertFuncs = append(revertFuncs, s.upsertCluster(r.Name, r, wg, nil)) 2193 } 2194 // Add new Routes 2195 for _, r := range new.Routes { 2196 revertFuncs = append(revertFuncs, s.upsertRoute(r.Name, r, nil, nil)) 2197 } 2198 if wg != nil && len(new.Clusters) > 0 { 2199 start := time.Now() 2200 log.Debug("UpdateEnvoyResources: Waiting for cluster updates to complete...") 2201 err := wg.Wait() 2202 if err != nil { 2203 log.Debug("UpdateEnvoyResources: cluster update failed: ", err) 2204 } 2205 log.Debug("UpdateEnvoyResources: Wait time for cluster updates: ", time.Since(start)) 2206 // new wait group for adds 2207 wg = completion.NewWaitGroup(ctx) 2208 } 2209 // Add new Listeners 2210 for _, r := range new.Listeners { 2211 listenerName := r.Name 2212 revertFuncs = append(revertFuncs, s.upsertListener(r.Name, r, wg, 2213 // this callback is not called if there is no change 2214 func(err error) { 2215 if err == nil && new.PortAllocationCallbacks[listenerName] != nil { 2216 if callbackErr := new.PortAllocationCallbacks[listenerName](ctx); callbackErr != nil { 2217 log.WithError(callbackErr).Warn("Failure in port allocation callback") 2218 } 2219 } 2220 })) 2221 } 2222 2223 if wg != nil { 2224 start := time.Now() 2225 log.Debug("UpdateEnvoyResources: Waiting for proxy updates to complete...") 2226 err := wg.Wait() 2227 log.Debugf("UpdateEnvoyResources: Wait time for proxy updates %v (err: %s)", time.Since(start), err) 2228 2229 // revert all changes in case of failure 2230 if err != nil { 2231 revertFuncs.Revert(nil) 2232 log.Debug("UpdateEnvoyResources: Finished reverting failed xDS transactions") 2233 } 2234 return err 2235 } 2236 return nil 2237 } 2238 2239 func (s *xdsServer) DeleteEnvoyResources(ctx context.Context, resources Resources) error { 2240 log.Debugf("DeleteEnvoyResources: Deleting %d listeners, %d routes, %d clusters, %d endpoints, and %d secrets...", 2241 len(resources.Listeners), len(resources.Routes), len(resources.Clusters), len(resources.Endpoints), len(resources.Secrets)) 2242 var wg *completion.WaitGroup 2243 var revertFuncs xds.AckingResourceMutatorRevertFuncList 2244 // Wait only if new Listeners are added, as they will always be acked. 2245 // (unreferenced routes or endpoints (and maybe clusters) are not ACKed or NACKed). 2246 if len(resources.Listeners) > 0 { 2247 wg = completion.NewWaitGroup(ctx) 2248 } 2249 for _, r := range resources.Listeners { 2250 listenerName := r.Name 2251 revertFuncs = append(revertFuncs, s.deleteListener(r.Name, wg, 2252 func(err error) { 2253 if err == nil && resources.PortAllocationCallbacks[listenerName] != nil { 2254 if callbackErr := resources.PortAllocationCallbacks[listenerName](ctx); callbackErr != nil { 2255 log.WithError(callbackErr).Warn("Failure in port allocation callback") 2256 } 2257 } 2258 })) 2259 } 2260 2261 // Do not wait for the deletion of routes, clusters, or endpoints, as 2262 // there are no guarantees that these deletions will be acked. For 2263 // example, if the listener referring to was already deleted earlier, 2264 // there are no references to the deleted resources anymore, in which 2265 // case we could wait forever for the ACKs. This could also happen if 2266 // there is no listener referring to other named resources to 2267 // begin with. 2268 for _, r := range resources.Routes { 2269 revertFuncs = append(revertFuncs, s.deleteRoute(r.Name, nil, nil)) 2270 } 2271 for _, r := range resources.Clusters { 2272 revertFuncs = append(revertFuncs, s.deleteCluster(r.Name, nil, nil)) 2273 } 2274 for _, r := range resources.Endpoints { 2275 revertFuncs = append(revertFuncs, s.deleteEndpoint(r.ClusterName, nil, nil)) 2276 } 2277 for _, r := range resources.Secrets { 2278 revertFuncs = append(revertFuncs, s.deleteSecret(r.Name, nil, nil)) 2279 } 2280 2281 if wg != nil { 2282 start := time.Now() 2283 log.Debug("DeleteEnvoyResources: Waiting for proxy updates to complete...") 2284 err := wg.Wait() 2285 log.Debugf("DeleteEnvoyResources: Wait time for proxy updates %v (err: %s)", time.Since(start), err) 2286 2287 // revert all changes in case of failure 2288 if err != nil { 2289 revertFuncs.Revert(nil) 2290 log.Debug("DeleteEnvoyResources: Finished reverting failed xDS transactions") 2291 } 2292 return err 2293 } 2294 return nil 2295 }