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  }