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 }