github.phpd.cn/cilium/cilium@v1.6.12/pkg/envoy/server.go (about) 1 // Copyright 2018 Authors of Cilium 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 envoy 16 17 import ( 18 "context" 19 "fmt" 20 "io/ioutil" 21 "net" 22 "os" 23 "path/filepath" 24 "sort" 25 "strings" 26 "syscall" 27 "time" 28 29 "github.com/cilium/cilium/pkg/bpf" 30 "github.com/cilium/cilium/pkg/completion" 31 "github.com/cilium/cilium/pkg/envoy/xds" 32 "github.com/cilium/cilium/pkg/identity" 33 "github.com/cilium/cilium/pkg/lock" 34 "github.com/cilium/cilium/pkg/option" 35 "github.com/cilium/cilium/pkg/policy" 36 "github.com/cilium/cilium/pkg/policy/api" 37 "github.com/cilium/cilium/pkg/proxy/logger" 38 39 cilium "github.com/cilium/proxy/go/cilium/api" 40 envoy_api_v2 "github.com/cilium/proxy/go/envoy/api/v2" 41 envoy_api_v2_core "github.com/cilium/proxy/go/envoy/api/v2/core" 42 envoy_api_v2_endpoint "github.com/cilium/proxy/go/envoy/api/v2/endpoint" 43 envoy_api_v2_listener "github.com/cilium/proxy/go/envoy/api/v2/listener" 44 envoy_api_v2_route "github.com/cilium/proxy/go/envoy/api/v2/route" 45 envoy_config_bootstrap_v2 "github.com/cilium/proxy/go/envoy/config/bootstrap/v2" 46 envoy_config_http "github.com/cilium/proxy/go/envoy/config/filter/network/http_connection_manager/v2" 47 envoy_config_tcp "github.com/cilium/proxy/go/envoy/config/filter/network/tcp_proxy/v2" 48 envoy_type_matcher "github.com/cilium/proxy/go/envoy/type/matcher" 49 50 "github.com/golang/protobuf/proto" 51 "github.com/golang/protobuf/ptypes" 52 "github.com/golang/protobuf/ptypes/any" 53 "github.com/golang/protobuf/ptypes/duration" 54 "github.com/golang/protobuf/ptypes/wrappers" 55 ) 56 57 var ( 58 // allowAllPortNetworkPolicy is a PortNetworkPolicy that allows all traffic 59 // to any L4 port. 60 allowAllPortNetworkPolicy = []*cilium.PortNetworkPolicy{ 61 // Allow all TCP traffic to any port. 62 {Protocol: envoy_api_v2_core.SocketAddress_TCP}, 63 // Allow all UDP traffic to any port. 64 {Protocol: envoy_api_v2_core.SocketAddress_UDP}, 65 } 66 ) 67 68 const ( 69 egressClusterName = "egress-cluster" 70 ingressClusterName = "ingress-cluster" 71 EnvoyTimeout = 300 * time.Second // must be smaller than endpoint.EndpointGenerationTimeout 72 ) 73 74 type Listener struct { 75 // must hold the XDSServer.mutex when accessing 'count' 76 count uint 77 78 // mutex is needed when accessing the fields below. 79 // XDSServer.mutex is not needed, but if taken it must be taken before 'mutex' 80 mutex lock.RWMutex 81 acked bool 82 nacked bool 83 waiters []*completion.Completion 84 } 85 86 // XDSServer provides a high-lever interface to manage resources published 87 // using the xDS gRPC API. 88 type XDSServer struct { 89 // socketPath is the path to the gRPC UNIX domain socket. 90 socketPath string 91 92 // accessLogPath is the path to the L7 access logs 93 accessLogPath string 94 95 // mutex protects accesses to the configuration resources below. 96 mutex lock.RWMutex 97 98 // listenerMutator publishes listener updates to Envoy proxies. 99 // Manages it's own locking 100 listenerMutator xds.AckingResourceMutator 101 102 // listeners is the set of names of listeners that have been added by 103 // calling AddListener. 104 // mutex must be held when accessing this. 105 // Value holds the number of redirects using the listener named by the key. 106 listeners map[string]*Listener 107 108 // networkPolicyCache publishes network policy configuration updates to 109 // Envoy proxies. 110 networkPolicyCache *xds.Cache 111 112 // NetworkPolicyMutator wraps networkPolicyCache to publish policy 113 // updates to Envoy proxies. 114 // Exported for testing only! 115 NetworkPolicyMutator xds.AckingResourceMutator 116 117 // networkPolicyEndpoints maps each network policy's name to the info on 118 // the local endpoint. 119 // mutex must be held when accessing this. 120 networkPolicyEndpoints map[string]logger.EndpointUpdater 121 122 // stopServer stops the xDS gRPC server. 123 stopServer context.CancelFunc 124 } 125 126 func getXDSPath(stateDir string) string { 127 return filepath.Join(stateDir, "xds.sock") 128 } 129 130 func toAny(pb proto.Message) *any.Any { 131 a, err := ptypes.MarshalAny(pb) 132 if err != nil { 133 panic(err.Error()) 134 } 135 return a 136 } 137 138 // StartXDSServer configures and starts the xDS GRPC server. 139 func StartXDSServer(stateDir string) *XDSServer { 140 xdsPath := getXDSPath(stateDir) 141 142 os.Remove(xdsPath) 143 socketListener, err := net.ListenUnix("unix", &net.UnixAddr{Name: xdsPath, Net: "unix"}) 144 if err != nil { 145 log.WithError(err).Fatalf("Envoy: Failed to open xDS listen socket at %s", xdsPath) 146 } 147 148 // Make the socket accessible by non-root Envoy proxies, e.g. running in 149 // sidecar containers. 150 if err = os.Chmod(xdsPath, 0777); err != nil { 151 log.WithError(err).Fatalf("Envoy: Failed to change mode of xDS listen socket at %s", xdsPath) 152 } 153 154 ldsCache := xds.NewCache() 155 ldsMutator := xds.NewAckingResourceMutatorWrapper(ldsCache) 156 ldsConfig := &xds.ResourceTypeConfiguration{ 157 Source: ldsCache, 158 AckObserver: ldsMutator, 159 } 160 161 npdsCache := xds.NewCache() 162 npdsMutator := xds.NewAckingResourceMutatorWrapper(npdsCache) 163 npdsConfig := &xds.ResourceTypeConfiguration{ 164 Source: npdsCache, 165 AckObserver: npdsMutator, 166 } 167 168 nphdsConfig := &xds.ResourceTypeConfiguration{ 169 Source: NetworkPolicyHostsCache, 170 AckObserver: &NetworkPolicyHostsCache, 171 } 172 173 stopServer := startXDSGRPCServer(socketListener, ldsConfig, npdsConfig, nphdsConfig, 5*time.Second) 174 175 return &XDSServer{ 176 socketPath: xdsPath, 177 accessLogPath: getAccessLogPath(stateDir), 178 listenerMutator: ldsMutator, 179 listeners: make(map[string]*Listener), 180 networkPolicyCache: npdsCache, 181 NetworkPolicyMutator: npdsMutator, 182 networkPolicyEndpoints: make(map[string]logger.EndpointUpdater), 183 stopServer: stopServer, 184 } 185 } 186 187 func (s *XDSServer) getHttpFilterChainProto(clusterName string) *envoy_api_v2_listener.FilterChain { 188 denied403body := option.Config.HTTP403Message 189 requestTimeout := int64(option.Config.HTTPRequestTimeout) // seconds 190 idleTimeout := int64(option.Config.HTTPIdleTimeout) // seconds 191 maxGRPCTimeout := int64(option.Config.HTTPMaxGRPCTimeout) // seconds 192 numRetries := uint32(option.Config.HTTPRetryCount) 193 retryTimeout := int64(option.Config.HTTPRetryTimeout) //seconds 194 195 hcmConfig := &envoy_config_http.HttpConnectionManager{ 196 StatPrefix: "proxy", 197 HttpFilters: []*envoy_config_http.HttpFilter{{ 198 Name: "cilium.l7policy", 199 ConfigType: &envoy_config_http.HttpFilter_TypedConfig{ 200 TypedConfig: toAny(&cilium.L7Policy{ 201 AccessLogPath: s.accessLogPath, 202 Denied_403Body: denied403body, 203 }), 204 }, 205 }, { 206 Name: "envoy.filters.http.router", 207 }}, 208 StreamIdleTimeout: &duration.Duration{}, // 0 == disabled 209 RouteSpecifier: &envoy_config_http.HttpConnectionManager_RouteConfig{ 210 RouteConfig: &envoy_api_v2.RouteConfiguration{ 211 VirtualHosts: []*envoy_api_v2_route.VirtualHost{{ 212 Name: "default_route", 213 Domains: []string{"*"}, 214 Routes: []*envoy_api_v2_route.Route{{ 215 Match: &envoy_api_v2_route.RouteMatch{ 216 PathSpecifier: &envoy_api_v2_route.RouteMatch_Prefix{Prefix: "/"}, 217 Grpc: &envoy_api_v2_route.RouteMatch_GrpcRouteMatchOptions{}, 218 }, 219 Action: &envoy_api_v2_route.Route_Route{ 220 Route: &envoy_api_v2_route.RouteAction{ 221 ClusterSpecifier: &envoy_api_v2_route.RouteAction_Cluster{ 222 Cluster: clusterName, 223 }, 224 Timeout: &duration.Duration{Seconds: requestTimeout}, 225 MaxGrpcTimeout: &duration.Duration{Seconds: maxGRPCTimeout}, 226 RetryPolicy: &envoy_api_v2_route.RetryPolicy{ 227 RetryOn: "5xx", 228 NumRetries: &wrappers.UInt32Value{Value: numRetries}, 229 PerTryTimeout: &duration.Duration{Seconds: retryTimeout}, 230 }, 231 }, 232 }, 233 }, { 234 Match: &envoy_api_v2_route.RouteMatch{ 235 PathSpecifier: &envoy_api_v2_route.RouteMatch_Prefix{Prefix: "/"}, 236 }, 237 Action: &envoy_api_v2_route.Route_Route{ 238 Route: &envoy_api_v2_route.RouteAction{ 239 ClusterSpecifier: &envoy_api_v2_route.RouteAction_Cluster{ 240 Cluster: clusterName, 241 }, 242 Timeout: &duration.Duration{Seconds: requestTimeout}, 243 //IdleTimeout: &duration.Duration{Seconds: idleTimeout}, 244 RetryPolicy: &envoy_api_v2_route.RetryPolicy{ 245 RetryOn: "5xx", 246 NumRetries: &wrappers.UInt32Value{Value: numRetries}, 247 PerTryTimeout: &duration.Duration{Seconds: retryTimeout}, 248 }, 249 }, 250 }, 251 }}, 252 }}, 253 }, 254 }, 255 } 256 257 // Idle timeout can only be specified if non-zero 258 if idleTimeout > 0 { 259 hcmConfig.GetRouteConfig().VirtualHosts[0].Routes[1].GetRoute().IdleTimeout = &duration.Duration{Seconds: idleTimeout} 260 } 261 262 chain := &envoy_api_v2_listener.FilterChain{ 263 Filters: []*envoy_api_v2_listener.Filter{{ 264 Name: "cilium.network", 265 }, { 266 Name: "envoy.filters.network.http_connection_manager", 267 ConfigType: &envoy_api_v2_listener.Filter_TypedConfig{ 268 TypedConfig: toAny(hcmConfig), 269 }, 270 }}, 271 } 272 273 return chain 274 } 275 276 func (s *XDSServer) getTcpFilterChainProto(clusterName string) *envoy_api_v2_listener.FilterChain { 277 return &envoy_api_v2_listener.FilterChain{ 278 Filters: []*envoy_api_v2_listener.Filter{{ 279 Name: "cilium.network", 280 ConfigType: &envoy_api_v2_listener.Filter_TypedConfig{ 281 TypedConfig: toAny(&cilium.NetworkFilter{ 282 Proxylib: "libcilium.so", 283 ProxylibParams: map[string]string{ 284 "access-log-path": s.accessLogPath, 285 "xds-path": s.socketPath, 286 }, 287 }), 288 }, 289 }, { 290 Name: "envoy.filters.network.tcp_proxy", 291 ConfigType: &envoy_api_v2_listener.Filter_TypedConfig{ 292 TypedConfig: toAny(&envoy_config_tcp.TcpProxy{ 293 StatPrefix: "tcp_proxy", 294 ClusterSpecifier: &envoy_config_tcp.TcpProxy_Cluster{ 295 Cluster: clusterName, 296 }, 297 }), 298 }, 299 }}, 300 } 301 } 302 303 // AddListener adds a listener to a running Envoy proxy. 304 func (s *XDSServer) AddListener(name string, kind policy.L7ParserType, port uint16, isIngress bool, mayUseOriginalSourceAddr bool, wg *completion.WaitGroup) { 305 log.Debugf("Envoy: %s AddListener %s (mayUseOriginalSourceAddr: %v)", kind, name, mayUseOriginalSourceAddr) 306 307 s.mutex.Lock() 308 listener := s.listeners[name] 309 if listener == nil { 310 listener = &Listener{} 311 s.listeners[name] = listener 312 } 313 listener.count++ 314 listener.mutex.Lock() // needed for other than 'count' 315 if listener.count > 1 && !listener.nacked { 316 log.Debugf("Envoy: Reusing listener: %s", name) 317 if !listener.acked { 318 // Listener not acked yet, add a completion to the waiter's list 319 log.Debugf("Envoy: Waiting for a non-acknowledged reused listener: %s", name) 320 listener.waiters = append(listener.waiters, wg.AddCompletion()) 321 } 322 listener.mutex.Unlock() 323 s.mutex.Unlock() 324 return 325 } 326 // Try again after a NACK, potentially with a different port number, etc. 327 if listener.nacked { 328 listener.acked = false 329 listener.nacked = false 330 } 331 listener.mutex.Unlock() // Listener locked again in callbacks below 332 333 clusterName := egressClusterName 334 socketMark := int64(0xB00) 335 if isIngress { 336 clusterName = ingressClusterName 337 socketMark = 0xA00 338 } 339 340 listenerConf := &envoy_api_v2.Listener{ 341 Name: name, 342 Address: &envoy_api_v2_core.Address{ 343 Address: &envoy_api_v2_core.Address_SocketAddress{ 344 SocketAddress: &envoy_api_v2_core.SocketAddress{ 345 Protocol: envoy_api_v2_core.SocketAddress_TCP, 346 Address: "::", 347 Ipv4Compat: true, 348 PortSpecifier: &envoy_api_v2_core.SocketAddress_PortValue{PortValue: uint32(port)}, 349 }, 350 }, 351 }, 352 Transparent: &wrappers.BoolValue{Value: true}, 353 SocketOptions: []*envoy_api_v2_core.SocketOption{{ 354 Description: "Listener socket mark", 355 Level: syscall.SOL_SOCKET, 356 Name: syscall.SO_MARK, 357 Value: &envoy_api_v2_core.SocketOption_IntValue{IntValue: socketMark}, 358 State: envoy_api_v2_core.SocketOption_STATE_PREBIND, 359 }}, 360 // FilterChains: []*envoy_api_v2_listener.FilterChain 361 ListenerFilters: []*envoy_api_v2_listener.ListenerFilter{{ 362 Name: "cilium.bpf_metadata", 363 ConfigType: &envoy_api_v2_listener.ListenerFilter_TypedConfig{ 364 TypedConfig: toAny(&cilium.BpfMetadata{ 365 IsIngress: isIngress, 366 MayUseOriginalSourceAddress: mayUseOriginalSourceAddr, 367 BpfRoot: bpf.GetMapRoot(), 368 }), 369 }, 370 }}, 371 } 372 373 // Add filter chains 374 if kind == policy.ParserTypeHTTP { 375 listenerConf.FilterChains = append(listenerConf.FilterChains, s.getHttpFilterChainProto(clusterName)) 376 } else { 377 listenerConf.FilterChains = append(listenerConf.FilterChains, s.getTcpFilterChainProto(clusterName)) 378 } 379 380 s.listenerMutator.Upsert(ListenerTypeURL, name, listenerConf, []string{"127.0.0.1"}, wg, 381 func(err error) { 382 // listener might have already been removed, so we can't look again 383 // but we still need to complete all the completions in case 384 // someone is still waiting! 385 listener.mutex.Lock() 386 if err == nil { 387 // Allow future users to not need to wait 388 listener.acked = true 389 } else { 390 // Prevent further reuse of a failed listener 391 listener.nacked = true 392 } 393 // Pass the completion result to all the additional waiters. 394 for _, waiter := range listener.waiters { 395 waiter.Complete(err) 396 } 397 listener.waiters = nil 398 listener.mutex.Unlock() 399 }) 400 s.mutex.Unlock() 401 } 402 403 // RemoveListener removes an existing Envoy Listener. 404 func (s *XDSServer) RemoveListener(name string, wg *completion.WaitGroup) xds.AckingResourceMutatorRevertFunc { 405 log.Debugf("Envoy: removeListener %s", name) 406 407 var listenerRevertFunc func(*completion.Completion) 408 409 s.mutex.Lock() 410 listener, ok := s.listeners[name] 411 if ok && listener != nil { 412 listener.count-- 413 if listener.count == 0 { 414 delete(s.listeners, name) 415 listenerRevertFunc = s.listenerMutator.Delete(ListenerTypeURL, name, []string{"127.0.0.1"}, wg, nil) 416 } 417 } else { 418 // Bail out if this listener does not exist 419 log.Fatalf("Envoy: Attempt to remove non-existent listener: %s", name) 420 } 421 s.mutex.Unlock() 422 423 return func(completion *completion.Completion) { 424 s.mutex.Lock() 425 if listenerRevertFunc != nil { 426 listenerRevertFunc(completion) 427 } 428 listener.count++ 429 s.listeners[name] = listener 430 s.mutex.Unlock() 431 } 432 } 433 434 func (s *XDSServer) stop() { 435 s.stopServer() 436 os.Remove(s.socketPath) 437 } 438 439 func getL7Rule(l7 *api.PortRuleL7) *cilium.L7NetworkPolicyRule { 440 rule := &cilium.L7NetworkPolicyRule{Rule: make(map[string]string, len(*l7))} 441 442 for k, v := range *l7 { 443 rule.Rule[k] = v 444 } 445 446 return rule // No ruleRef 447 } 448 449 func getHTTPRule(h *api.PortRuleHTTP) []*envoy_api_v2_route.HeaderMatcher { 450 // Count the number of header matches we need 451 cnt := len(h.Headers) 452 if h.Path != "" { 453 cnt++ 454 } 455 if h.Method != "" { 456 cnt++ 457 } 458 if h.Host != "" { 459 cnt++ 460 } 461 462 googleRe2 := &envoy_type_matcher.RegexMatcher_GoogleRe2{ 463 GoogleRe2: &envoy_type_matcher.RegexMatcher_GoogleRE2{ 464 MaxProgramSize: &wrappers.UInt32Value{Value: 100}, // Envoy default 465 }} 466 467 headers := make([]*envoy_api_v2_route.HeaderMatcher, 0, cnt) 468 if h.Path != "" { 469 headers = append(headers, &envoy_api_v2_route.HeaderMatcher{ 470 Name: ":path", 471 HeaderMatchSpecifier: &envoy_api_v2_route.HeaderMatcher_SafeRegexMatch{ 472 SafeRegexMatch: &envoy_type_matcher.RegexMatcher{ 473 EngineType: googleRe2, 474 Regex: h.Path, 475 }}}) 476 } 477 if h.Method != "" { 478 headers = append(headers, &envoy_api_v2_route.HeaderMatcher{ 479 Name: ":method", 480 HeaderMatchSpecifier: &envoy_api_v2_route.HeaderMatcher_SafeRegexMatch{ 481 SafeRegexMatch: &envoy_type_matcher.RegexMatcher{ 482 EngineType: googleRe2, 483 Regex: h.Method, 484 }}}) 485 } 486 if h.Host != "" { 487 headers = append(headers, &envoy_api_v2_route.HeaderMatcher{ 488 Name: ":authority", 489 HeaderMatchSpecifier: &envoy_api_v2_route.HeaderMatcher_SafeRegexMatch{ 490 SafeRegexMatch: &envoy_type_matcher.RegexMatcher{ 491 EngineType: googleRe2, 492 Regex: h.Host, 493 }}}) 494 } 495 for _, hdr := range h.Headers { 496 strs := strings.SplitN(hdr, " ", 2) 497 if len(strs) == 2 { 498 // Remove ':' in "X-Key: true" 499 key := strings.TrimRight(strs[0], ":") 500 // Header presence and matching (literal) value needed. 501 headers = append(headers, &envoy_api_v2_route.HeaderMatcher{Name: key, 502 HeaderMatchSpecifier: &envoy_api_v2_route.HeaderMatcher_ExactMatch{ExactMatch: strs[1]}}) 503 } else { 504 // Only header presence needed 505 headers = append(headers, &envoy_api_v2_route.HeaderMatcher{Name: strs[0], 506 HeaderMatchSpecifier: &envoy_api_v2_route.HeaderMatcher_PresentMatch{PresentMatch: true}}) 507 } 508 } 509 if len(headers) == 0 { 510 headers = nil 511 } else { 512 SortHeaderMatchers(headers) 513 } 514 return headers 515 } 516 517 func createBootstrap(filePath string, nodeId, cluster string, xdsSock, egressClusterName, ingressClusterName string, adminPath string) { 518 connectTimeout := int64(option.Config.ProxyConnectTimeout) // in seconds 519 520 bs := &envoy_config_bootstrap_v2.Bootstrap{ 521 Node: &envoy_api_v2_core.Node{Id: nodeId, Cluster: cluster}, 522 StaticResources: &envoy_config_bootstrap_v2.Bootstrap_StaticResources{ 523 Clusters: []*envoy_api_v2.Cluster{ 524 { 525 Name: egressClusterName, 526 ClusterDiscoveryType: &envoy_api_v2.Cluster_Type{Type: envoy_api_v2.Cluster_ORIGINAL_DST}, 527 ConnectTimeout: &duration.Duration{Seconds: connectTimeout, Nanos: 0}, 528 CleanupInterval: &duration.Duration{Seconds: connectTimeout, Nanos: 500000000}, 529 LbPolicy: envoy_api_v2.Cluster_ORIGINAL_DST_LB, 530 ProtocolSelection: envoy_api_v2.Cluster_USE_DOWNSTREAM_PROTOCOL, 531 }, 532 { 533 Name: ingressClusterName, 534 ClusterDiscoveryType: &envoy_api_v2.Cluster_Type{Type: envoy_api_v2.Cluster_ORIGINAL_DST}, 535 ConnectTimeout: &duration.Duration{Seconds: connectTimeout, Nanos: 0}, 536 CleanupInterval: &duration.Duration{Seconds: connectTimeout, Nanos: 500000000}, 537 LbPolicy: envoy_api_v2.Cluster_ORIGINAL_DST_LB, 538 ProtocolSelection: envoy_api_v2.Cluster_USE_DOWNSTREAM_PROTOCOL, 539 }, 540 { 541 Name: "xds-grpc-cilium", 542 ClusterDiscoveryType: &envoy_api_v2.Cluster_Type{Type: envoy_api_v2.Cluster_STATIC}, 543 ConnectTimeout: &duration.Duration{Seconds: connectTimeout, Nanos: 0}, 544 LbPolicy: envoy_api_v2.Cluster_ROUND_ROBIN, 545 LoadAssignment: &envoy_api_v2.ClusterLoadAssignment{ 546 ClusterName: "xds-grpc-cilium", 547 Endpoints: []*envoy_api_v2_endpoint.LocalityLbEndpoints{{ 548 LbEndpoints: []*envoy_api_v2_endpoint.LbEndpoint{{ 549 HostIdentifier: &envoy_api_v2_endpoint.LbEndpoint_Endpoint{ 550 Endpoint: &envoy_api_v2_endpoint.Endpoint{ 551 Address: &envoy_api_v2_core.Address{ 552 Address: &envoy_api_v2_core.Address_Pipe{ 553 Pipe: &envoy_api_v2_core.Pipe{Path: xdsSock}}, 554 }, 555 }, 556 }, 557 }}, 558 }}, 559 }, 560 Http2ProtocolOptions: &envoy_api_v2_core.Http2ProtocolOptions{}, 561 }, 562 }, 563 }, 564 DynamicResources: &envoy_config_bootstrap_v2.Bootstrap_DynamicResources{ 565 LdsConfig: &envoy_api_v2_core.ConfigSource{ 566 ConfigSourceSpecifier: &envoy_api_v2_core.ConfigSource_ApiConfigSource{ 567 ApiConfigSource: &envoy_api_v2_core.ApiConfigSource{ 568 ApiType: envoy_api_v2_core.ApiConfigSource_GRPC, 569 SetNodeOnFirstMessageOnly: true, 570 GrpcServices: []*envoy_api_v2_core.GrpcService{ 571 { 572 TargetSpecifier: &envoy_api_v2_core.GrpcService_EnvoyGrpc_{ 573 EnvoyGrpc: &envoy_api_v2_core.GrpcService_EnvoyGrpc{ 574 ClusterName: "xds-grpc-cilium", 575 }, 576 }, 577 }, 578 }, 579 }, 580 }, 581 }, 582 }, 583 Admin: &envoy_config_bootstrap_v2.Admin{ 584 AccessLogPath: "/dev/null", 585 Address: &envoy_api_v2_core.Address{ 586 Address: &envoy_api_v2_core.Address_Pipe{ 587 Pipe: &envoy_api_v2_core.Pipe{Path: adminPath}, 588 }, 589 }, 590 }, 591 } 592 593 log.Debugf("Envoy: Bootstrap: %s", bs) 594 data, err := proto.Marshal(bs) 595 if err != nil { 596 log.WithError(err).Fatal("Envoy: Error marshaling Envoy bootstrap") 597 } 598 err = ioutil.WriteFile(filePath, data, 0644) 599 if err != nil { 600 log.WithError(err).Fatal("Envoy: Error writing Envoy bootstrap file") 601 } 602 } 603 604 func getPortNetworkPolicyRule(sel policy.CachedSelector, l7Parser policy.L7ParserType, l7Rules api.L7Rules) *cilium.PortNetworkPolicyRule { 605 // Optimize the policy if the endpoint selector is a wildcard by 606 // keeping remote policies list empty to match all remote policies. 607 var remotePolicies []uint64 608 if !sel.IsWildcard() { 609 for _, id := range sel.GetSelections() { 610 remotePolicies = append(remotePolicies, uint64(id)) 611 } 612 613 // No remote policies would match this rule. Discard it. 614 if len(remotePolicies) == 0 { 615 return nil 616 } 617 618 sort.Slice(remotePolicies, func(i, j int) bool { 619 return remotePolicies[i] < remotePolicies[j] 620 }) 621 } 622 623 r := &cilium.PortNetworkPolicyRule{ 624 RemotePolicies: remotePolicies, 625 } 626 627 switch l7Parser { 628 case policy.ParserTypeHTTP: 629 if len(l7Rules.HTTP) > 0 { // Just cautious. This should never be false. 630 httpRules := make([]*cilium.HttpNetworkPolicyRule, 0, len(l7Rules.HTTP)) 631 for _, l7 := range l7Rules.HTTP { 632 headers := getHTTPRule(&l7) 633 httpRules = append(httpRules, &cilium.HttpNetworkPolicyRule{Headers: headers}) 634 } 635 SortHTTPNetworkPolicyRules(httpRules) 636 r.L7 = &cilium.PortNetworkPolicyRule_HttpRules{ 637 HttpRules: &cilium.HttpNetworkPolicyRules{ 638 HttpRules: httpRules, 639 }, 640 } 641 } 642 case policy.ParserTypeKafka: 643 // TODO: Support Kafka. For now, just ignore any Kafka L7 rule. 644 645 case policy.ParserTypeDNS: 646 // TODO: Support DNS. For now, just ignore any DNS L7 rule. 647 648 default: 649 // Assume unknown parser types use a Key-Value Pair policy 650 if len(l7Rules.L7) > 0 { 651 kvpRules := make([]*cilium.L7NetworkPolicyRule, 0, len(l7Rules.L7)) 652 for _, l7 := range l7Rules.L7 { 653 kvpRules = append(kvpRules, getL7Rule(&l7)) 654 } 655 // L7 rules are not sorted 656 r.L7Proto = l7Parser.String() 657 r.L7 = &cilium.PortNetworkPolicyRule_L7Rules{ 658 L7Rules: &cilium.L7NetworkPolicyRules{ 659 L7Rules: kvpRules, 660 }, 661 } 662 } 663 } 664 665 return r 666 } 667 668 func getDirectionNetworkPolicy(l4Policy policy.L4PolicyMap, policyEnforced bool) []*cilium.PortNetworkPolicy { 669 if !policyEnforced { 670 // Return an allow-all policy. 671 return allowAllPortNetworkPolicy 672 } 673 674 if len(l4Policy) == 0 { 675 return nil 676 } 677 678 PerPortPolicies := make([]*cilium.PortNetworkPolicy, 0, len(l4Policy)) 679 680 for _, l4 := range l4Policy { 681 var protocol envoy_api_v2_core.SocketAddress_Protocol 682 switch l4.Protocol { 683 case api.ProtoTCP: 684 protocol = envoy_api_v2_core.SocketAddress_TCP 685 case api.ProtoUDP: 686 protocol = envoy_api_v2_core.SocketAddress_UDP 687 } 688 689 pnp := &cilium.PortNetworkPolicy{ 690 Port: uint32(l4.Port), 691 Protocol: protocol, 692 Rules: make([]*cilium.PortNetworkPolicyRule, 0, len(l4.L7RulesPerEp)), 693 } 694 695 allowAll := false 696 for sel, l7 := range l4.L7RulesPerEp { 697 rule := getPortNetworkPolicyRule(sel, l4.L7Parser, l7) 698 if rule != nil { 699 if len(rule.RemotePolicies) == 0 && rule.L7 == nil { 700 // Got an allow-all rule, which would short-circuit all of 701 // the other rules. Just set no rules, which has the same 702 // effect of allowing all. 703 allowAll = true 704 pnp.Rules = nil 705 break 706 } 707 708 pnp.Rules = append(pnp.Rules, rule) 709 } 710 } 711 712 // No rule for this port matches any remote identity. 713 // This means that no traffic was explicitly allowed for this port. 714 // In this case, just don't generate any PortNetworkPolicy for this 715 // port. 716 if !allowAll && len(pnp.Rules) == 0 { 717 continue 718 } 719 720 SortPortNetworkPolicyRules(pnp.Rules) 721 722 PerPortPolicies = append(PerPortPolicies, pnp) 723 } 724 725 if len(PerPortPolicies) == 0 { 726 return nil 727 } 728 729 SortPortNetworkPolicies(PerPortPolicies) 730 731 return PerPortPolicies 732 } 733 734 // getNetworkPolicy converts a network policy into a cilium.NetworkPolicy. 735 func getNetworkPolicy(name string, id identity.NumericIdentity, conntrackName string, policy *policy.L4Policy, 736 ingressPolicyEnforced, egressPolicyEnforced bool) *cilium.NetworkPolicy { 737 p := &cilium.NetworkPolicy{ 738 Name: name, 739 Policy: uint64(id), 740 ConntrackMapName: conntrackName, 741 } 742 743 // If no policy, deny all traffic. Otherwise, convert the policies for ingress and egress. 744 if policy != nil { 745 p.IngressPerPortPolicies = getDirectionNetworkPolicy(policy.Ingress, ingressPolicyEnforced) 746 p.EgressPerPortPolicies = getDirectionNetworkPolicy(policy.Egress, egressPolicyEnforced) 747 } 748 749 return p 750 } 751 752 // return the Envoy proxy node IDs that need to ACK the policy. 753 func getNodeIDs(ep logger.EndpointUpdater, policy *policy.L4Policy) []string { 754 nodeIDs := make([]string, 0, 1) 755 if ep.HasSidecarProxy() { 756 // Istio sidecars have the Cilium bpf metadata filter 757 // statically configured running the NPDS client, so 758 // we may unconditionally wait for ACKs from the 759 // sidecars. 760 // Sidecar's IPv4 address is used as the node ID. 761 ipv4 := ep.GetIPv4Address() 762 if ipv4 == "" { 763 log.Error("Envoy: Sidecar proxy has no IPv4 address") 764 } else { 765 nodeIDs = append(nodeIDs, ipv4) 766 } 767 } else { 768 // Host proxy uses "127.0.0.1" as the nodeID 769 nodeIDs = append(nodeIDs, "127.0.0.1") 770 } 771 // Require additional ACK from proxylib if policy has proxylib redirects 772 // Note that if a previous policy had a proxylib redirect and this one does not, 773 // we only wait for the ACK from the main Envoy node ID. 774 if policy.HasProxylibRedirect() { 775 // Proxylib uses "127.0.0.2" as the nodeID 776 nodeIDs = append(nodeIDs, "127.0.0.2") 777 } 778 return nodeIDs 779 } 780 781 // UpdateNetworkPolicy adds or updates a network policy in the set published 782 // to L7 proxies. 783 // When the proxy acknowledges the network policy update, it will result in 784 // a subsequent call to the endpoint's OnProxyPolicyUpdate() function. 785 func (s *XDSServer) UpdateNetworkPolicy(ep logger.EndpointUpdater, policy *policy.L4Policy, 786 ingressPolicyEnforced, egressPolicyEnforced bool, wg *completion.WaitGroup) (error, func() error) { 787 788 s.mutex.Lock() 789 defer s.mutex.Unlock() 790 791 // First, validate all policies 792 ips := []string{ 793 ep.GetIPv6Address(), 794 ep.GetIPv4Address(), 795 } 796 var policies []*cilium.NetworkPolicy 797 for _, ip := range ips { 798 if ip == "" { 799 continue 800 } 801 networkPolicy := getNetworkPolicy(ip, ep.GetIdentityLocked(), ep.ConntrackName(), policy, 802 ingressPolicyEnforced, egressPolicyEnforced) 803 err := networkPolicy.Validate() 804 if err != nil { 805 return fmt.Errorf("error validating generated NetworkPolicy for %s: %s", ip, err), nil 806 } 807 policies = append(policies, networkPolicy) 808 } 809 810 nodeIDs := getNodeIDs(ep, policy) 811 812 // If there are no listeners configured, the local node's Envoy proxy won't 813 // query for network policies and therefore will never ACK them, and we'd 814 // wait forever. 815 if !ep.HasSidecarProxy() { 816 if len(s.listeners) == 0 { 817 wg = nil 818 } 819 } 820 821 // When successful, push them into the cache. 822 revertFuncs := make([]xds.AckingResourceMutatorRevertFunc, 0, len(policies)) 823 revertUpdatedNetworkPolicyEndpoints := make(map[string]logger.EndpointUpdater, len(policies)) 824 for _, p := range policies { 825 var callback func(error) 826 if policy != nil { 827 policyRevision := policy.Revision 828 callback = func(err error) { 829 if err == nil { 830 go ep.OnProxyPolicyUpdate(policyRevision) 831 } 832 } 833 } 834 revertFuncs = append(revertFuncs, s.NetworkPolicyMutator.Upsert(NetworkPolicyTypeURL, p.Name, p, nodeIDs, wg, callback)) 835 revertUpdatedNetworkPolicyEndpoints[p.Name] = s.networkPolicyEndpoints[p.Name] 836 s.networkPolicyEndpoints[p.Name] = ep 837 } 838 839 return nil, func() error { 840 log.Debug("Reverting xDS network policy update") 841 842 s.mutex.Lock() 843 defer s.mutex.Unlock() 844 845 for name, ep := range revertUpdatedNetworkPolicyEndpoints { 846 if ep == nil { 847 delete(s.networkPolicyEndpoints, name) 848 } else { 849 s.networkPolicyEndpoints[name] = ep 850 } 851 } 852 853 // Don't wait for an ACK for the reverted xDS updates. 854 // This is best-effort. 855 for _, revertFunc := range revertFuncs { 856 revertFunc(completion.NewCompletion(nil, nil)) 857 } 858 859 log.Debug("Finished reverting xDS network policy update") 860 861 return nil 862 } 863 } 864 865 // UseCurrentNetworkPolicy inserts a Completion to the WaitGroup if the current network policy has not yet been acked. 866 // 'wg' may not be nil. 867 func (s *XDSServer) UseCurrentNetworkPolicy(ep logger.EndpointUpdater, policy *policy.L4Policy, wg *completion.WaitGroup) { 868 s.mutex.Lock() 869 defer s.mutex.Unlock() 870 871 // If there are no listeners configured, the local node's Envoy proxy won't 872 // query for network policies and therefore will never ACK them, and we'd 873 // wait forever. 874 if !ep.HasSidecarProxy() && len(s.listeners) == 0 { 875 return 876 } 877 878 nodeIDs := getNodeIDs(ep, policy) 879 s.NetworkPolicyMutator.UseCurrent(NetworkPolicyTypeURL, nodeIDs, wg) 880 } 881 882 // RemoveNetworkPolicy removes network policies relevant to the specified 883 // endpoint from the set published to L7 proxies, and stops listening for 884 // acks for policies on this endpoint. 885 func (s *XDSServer) RemoveNetworkPolicy(ep logger.EndpointInfoSource) { 886 s.mutex.Lock() 887 defer s.mutex.Unlock() 888 889 name := ep.GetIPv6Address() 890 if name != "" { 891 s.networkPolicyCache.Delete(NetworkPolicyTypeURL, name) 892 delete(s.networkPolicyEndpoints, name) 893 } 894 name = ep.GetIPv4Address() 895 if name != "" { 896 s.networkPolicyCache.Delete(NetworkPolicyTypeURL, name) 897 delete(s.networkPolicyEndpoints, name) 898 // Delete node resources held in the cache for the endpoint (e.g., sidecar) 899 s.NetworkPolicyMutator.DeleteNode(name) 900 } 901 } 902 903 // RemoveAllNetworkPolicies removes all network policies from the set published 904 // to L7 proxies. 905 func (s *XDSServer) RemoveAllNetworkPolicies() { 906 s.networkPolicyCache.Clear(NetworkPolicyTypeURL) 907 } 908 909 // GetNetworkPolicies returns the current version of the network policies with 910 // the given names. 911 // If resourceNames is empty, all resources are returned. 912 func (s *XDSServer) GetNetworkPolicies(resourceNames []string) (map[string]*cilium.NetworkPolicy, error) { 913 resources, err := s.networkPolicyCache.GetResources(context.Background(), NetworkPolicyTypeURL, 0, "", resourceNames) 914 if err != nil { 915 return nil, err 916 } 917 networkPolicies := make(map[string]*cilium.NetworkPolicy, len(resources.Resources)) 918 for _, res := range resources.Resources { 919 networkPolicy := res.(*cilium.NetworkPolicy) 920 networkPolicies[networkPolicy.Name] = networkPolicy 921 } 922 return networkPolicies, nil 923 } 924 925 // getLocalEndpoint returns the endpoint info for the local endpoint on which 926 // the network policy of the given name if enforced, or nil if not found. 927 func (s *XDSServer) getLocalEndpoint(networkPolicyName string) logger.EndpointUpdater { 928 s.mutex.RLock() 929 defer s.mutex.RUnlock() 930 931 return s.networkPolicyEndpoints[networkPolicyName] 932 }