github.com/aporeto-inc/trireme-lib@v10.358.0+incompatible/utils/cri/cri_client_setup_windows.go (about)

     1  // +build windows
     2  
     3  package cri
     4  
     5  import (
     6  	"context"
     7  	"fmt"
     8  	"net"
     9  	"net/url"
    10  	"os"
    11  	"path"
    12  	"regexp"
    13  	"strings"
    14  
    15  	"github.com/Microsoft/go-winio"
    16  	"go.aporeto.io/enforcerd/internal/utils"
    17  	"go.uber.org/zap"
    18  	"google.golang.org/grpc"
    19  	criruntimev1alpha2 "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
    20  )
    21  
    22  const (
    23  	criDockerShimEndpoint    = "//./pipe/dockershim"
    24  	criContainerdOldEndpoint = "//./pipe/containerd"
    25  	criContainerdEndpoint    = "//./pipe/containerd-containerd"
    26  	criCrioEndpoint          = "//./pipe/crio"
    27  )
    28  
    29  // ParseStringFlag parses a flag from a given command
    30  func ParseStringFlag(cmd string, flagRegexp string) *string {
    31  	flags := ParseStringFlags(cmd, flagRegexp)
    32  	if len(flags) > 0 {
    33  		return &flags[0]
    34  	}
    35  	return nil
    36  }
    37  
    38  // flagTemplate captures CLI flags (i.e., 'cmd --some-flag=value', 'cmd --some-flag value', 'cmd --some-flag=valA --some-flag=valB')
    39  const flagTemplate = `(?:%s)(?:=|\s+)(\S+)`
    40  
    41  // ParseStringFlags parses a list of flags from a given command
    42  func ParseStringFlags(cmd string, flagRegexp string) []string {
    43  	var res []string
    44  	expression := fmt.Sprintf(flagTemplate, flagRegexp)
    45  	matches := regexp.MustCompile(expression).FindAllStringSubmatch(cmd, -1)
    46  	for _, tokens := range matches {
    47  		if len(tokens) > 1 {
    48  			res = append(res, strings.Trim(tokens[1], `"'`))
    49  		}
    50  	}
    51  	return res
    52  }
    53  
    54  // BuildProcessRegex returns a regex that should match processes with a name matching the given process regular
    55  // expression
    56  // Remark: procExpression can be a regular expression
    57  func BuildProcessRegex(procExpression string) *regexp.Regexp {
    58  	// Expressions that should be matched by a given procname are:
    59  	// procname -flag1 -flag2
    60  	// /bin/procname -flag1 -flag2
    61  	// /procname -flag1 -flag2
    62  	//
    63  	// Expressions that should NOT be matched are:
    64  	// notprocname -flag1 -flag2
    65  	// /bin/notprocname -flag1 -flag2
    66  	// /bin/procname/notprocname -flag1 -flag2
    67  	// notprocname -flag1 procname
    68  	// notprocname -flag1 -procname
    69  	return regexp.MustCompile(fmt.Sprintf(`^(\S*/)?(%s)( |$)`, procExpression))
    70  }
    71  
    72  // KubeletProcessRegex is the kubelet process regex used to find the kubelet process
    73  // Sometimes it is not the kubelet binary that is used in the system (e.g. Openshift4) but k8s' all-in-one binary: https://github.com/kubernetes/kubernetes/tree/master/cluster/images/hyperkube
    74  // The following is an example of a kubelet cmdline in Openshift4:
    75  // /usr/bin/hyperkube kubelet --config=/etc/kubernetes/kubelet.conf --bootstrap-kubeconfig=/etc/kubernete s/kubeconfig --rotate-certificates --kubeconfig=/var/lib/kubelet/kubeconfig --container-runtime=remote --container-runtime-endpoint=/var/run/crio/crio.s ock --allow-privileged --node-labels=node-role.kubernetes.io/master --minimum-container-ttl-duration=6m0s --client-ca-file=/etc/kubernetes/ca.crt --clou d-provider=aws --anonymous-auth=false --register-with-taints=node-role.kubernetes.io/master=:NoSchedule
    76  var KubeletProcessRegex = BuildProcessRegex("(hyperkube )?kubelet")
    77  
    78  // CriSocket returns the CRI socket path used by kubelet
    79  func CriSocket() (string, error) { //nolint
    80  	procs, err := utils.Processes()
    81  	if err != nil {
    82  		return "", fmt.Errorf("failed to list processes: %v", err)
    83  	}
    84  	for _, proc := range procs {
    85  		if KubeletProcessRegex.MatchString(proc.Cmdline) {
    86  			if sock := ParseStringFlag(proc.Cmdline, "--container-runtime-endpoint"); sock != nil {
    87  				return strings.TrimPrefix(*sock, "npipe://"), nil
    88  			}
    89  		}
    90  	}
    91  	return "", nil
    92  }
    93  
    94  // DetectCRIRuntimeEndpoint checks if the unix socket path are present for CRI
    95  func DetectCRIRuntimeEndpoint() (string, Type, error) {
    96  	var retErr error
    97  	isNamedPipe := func(path string) (string, error) {
    98  		_, err := os.Stat(path)
    99  		if err != nil {
   100  			return "", fmt.Errorf("%s not a named pipe", path)
   101  		}
   102  		return path, nil
   103  	}
   104  	runtimes := []string{criDockerShimEndpoint, criContainerdEndpoint, criContainerdOldEndpoint, criCrioEndpoint}
   105  	existingCriSockets := []string{}
   106  	for _, p := range runtimes {
   107  		if addr, err := isNamedPipe(p); err == nil {
   108  			zap.L().Debug("cri: detected CRI runtime service socket address", zap.String("socketPathAddress", addr))
   109  			existingCriSockets = append(existingCriSockets, addr)
   110  		} else {
   111  			retErr = err
   112  			zap.L().Debug("cri: socket path unavailable/inaccessible", zap.String("socketPath", p), zap.Error(err))
   113  		}
   114  	}
   115  
   116  	zap.L().Debug("The CRI sockets found are:", zap.Strings("paths", existingCriSockets))
   117  	if len(existingCriSockets) > 1 {
   118  		// this should ideally not happen but if it happens then get the kubelet cmdLine CRI
   119  		// now check for the kubelet runtime
   120  		sockaddr, err := CriSocket()
   121  		if err != nil {
   122  			return sockaddr, getCRISocketAddrType(sockaddr), fmt.Errorf("Multiple detection of CRI runtime endpoints, failed to get socketPath from kubelet")
   123  		}
   124  		// If there is no CRI EP on kubelet that means docker is the default, because kubelet's default CRI is docker.
   125  		if sockaddr == "" {
   126  			return criDockerShimEndpoint, TypeDocker, nil
   127  		}
   128  		return sockaddr, getCRISocketAddrType(sockaddr), nil
   129  	} else if len(existingCriSockets) == 1 {
   130  		return existingCriSockets[0], getCRISocketAddrType(existingCriSockets[0]), nil
   131  	}
   132  	// no CRI endpoint present on the system/node, this can happen during restarts
   133  	return "", TypeNone, fmt.Errorf("auto detection of CRI runtime endpoints failed, tested common locations %s, %s", strings.Join(runtimes, ", "), retErr)
   134  }
   135  
   136  func getCRISocketAddrType(sockaddr string) Type {
   137  	if strings.Contains(sockaddr, "crio") {
   138  		return TypeCRIO
   139  	}
   140  	if strings.Contains(sockaddr, "containerd") {
   141  		return TypeContainerD
   142  	}
   143  	if strings.Contains(sockaddr, "docker") {
   144  		return TypeDocker
   145  	}
   146  	return TypeNone
   147  }
   148  
   149  func getCRISocketAddr(criRuntimeEndpoint string) (string, error) {
   150  	var err error
   151  	// url.Parse only supports forward slashes
   152  	addr := strings.Replace(criRuntimeEndpoint, "\\", "/", -1)
   153  	if addr == "" {
   154  		addr, _, err = DetectCRIRuntimeEndpoint()
   155  		if err != nil {
   156  			return "", err
   157  		}
   158  	}
   159  	if strings.HasPrefix(addr, "tcp:") {
   160  		return "", fmt.Errorf("tcp endpoints are not supported")
   161  	}
   162  	if !strings.HasPrefix(addr, "npipe:") {
   163  		addr = "npipe://" + addr
   164  	}
   165  	addr = path.Clean(addr)
   166  
   167  	if strings.Contains(addr, "frakti") {
   168  		return "", fmt.Errorf("frakti runtime is not supported")
   169  	}
   170  
   171  	u, err := url.Parse(addr)
   172  	if err != nil {
   173  		return "", err
   174  	}
   175  	if u.Scheme != "npipe" {
   176  		return "", fmt.Errorf("only named pipes are supported")
   177  	}
   178  	if u.Host != "." {
   179  		return "", fmt.Errorf("only named pipes on the local host are supported")
   180  	}
   181  
   182  	return fmt.Sprintf("//%s%s", u.Host, u.Path), nil
   183  }
   184  
   185  func connectCRISocket(ctx context.Context, addr string) (*grpc.ClientConn, error) {
   186  	var err error
   187  	var connection *grpc.ClientConn
   188  
   189  	ctx, cancel := context.WithTimeout(ctx, connectTimeout)
   190  	defer cancel()
   191  
   192  	connection, err = grpc.DialContext(
   193  		ctx,
   194  		addr,
   195  		// we want to wait for an initial connection
   196  		grpc.WithBlock(),
   197  		// we do everything like the kubelet: we bump this up to 16MB
   198  		grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(maxMsgSize)),
   199  		// unix socket connection, disable transport security
   200  		grpc.WithInsecure(),
   201  		grpc.WithContextDialer(func(ctx context.Context, addr string) (net.Conn, error) {
   202  			return winio.DialPipeContext(ctx, addr)
   203  		}),
   204  	)
   205  	if err != nil {
   206  		return nil, fmt.Errorf("connection to CRI runtime service failed: %s", err.Error())
   207  	}
   208  	return connection, nil
   209  }
   210  
   211  // NewCRIRuntimeServiceClient takes a CRI socket path and tries to establish a grpc connection to the CRI runtime service.
   212  // On success it is returning an ExtendedRuntimeService interface which is an extended CRI runtime service interface.
   213  func NewCRIRuntimeServiceClient(ctx context.Context, criRuntimeEndpoint string) (ExtendedRuntimeService, error) {
   214  	// build the socket path URL
   215  	addr, err := getCRISocketAddr(criRuntimeEndpoint)
   216  	if err != nil {
   217  		return nil, fmt.Errorf("cri: failed to get socket address: %s", err)
   218  	}
   219  
   220  	// establish the CRI connection
   221  	// once this connection has been established
   222  	// gRPC will take care of reconnections, etc.
   223  	// connections are very much hands-off after that point
   224  	connection, err := connectCRISocket(ctx, addr)
   225  	if err != nil {
   226  		return nil, fmt.Errorf("cri: failed to connect to CRI socket: %s", err)
   227  	}
   228  
   229  	// finally create the extended wrapper
   230  	svc, err := NewCRIExtendedRuntimeServiceWrapper(
   231  		ctx,
   232  		callTimeout,
   233  		criruntimev1alpha2.NewRuntimeServiceClient(connection),
   234  	)
   235  	if err != nil {
   236  		return nil, fmt.Errorf("faile to create extended runtime service wrapper: %s", err.Error())
   237  	}
   238  
   239  	// and return with it
   240  	return svc, nil
   241  }