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