github.com/telepresenceio/telepresence/v2@v2.20.0-pro.6.0.20240517030216-236ea954e789/pkg/client/userd/daemon/grpc.go (about) 1 package daemon 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/json" 7 "errors" 8 "fmt" 9 "io" 10 "runtime" 11 "strings" 12 "sync/atomic" 13 "time" 14 15 "go.opentelemetry.io/otel" 16 "go.opentelemetry.io/otel/trace" 17 "google.golang.org/grpc/codes" 18 "google.golang.org/grpc/status" 19 "google.golang.org/protobuf/types/known/emptypb" 20 empty "google.golang.org/protobuf/types/known/emptypb" 21 22 "github.com/datawire/dlib/derror" 23 "github.com/datawire/dlib/dexec" 24 "github.com/datawire/dlib/dgroup" 25 "github.com/datawire/dlib/dlog" 26 "github.com/telepresenceio/telepresence/rpc/v2/common" 27 rpc "github.com/telepresenceio/telepresence/rpc/v2/connector" 28 "github.com/telepresenceio/telepresence/rpc/v2/daemon" 29 "github.com/telepresenceio/telepresence/rpc/v2/manager" 30 "github.com/telepresenceio/telepresence/v2/pkg/client" 31 "github.com/telepresenceio/telepresence/v2/pkg/client/logging" 32 "github.com/telepresenceio/telepresence/v2/pkg/client/scout" 33 "github.com/telepresenceio/telepresence/v2/pkg/client/socket" 34 "github.com/telepresenceio/telepresence/v2/pkg/client/userd" 35 "github.com/telepresenceio/telepresence/v2/pkg/errcat" 36 "github.com/telepresenceio/telepresence/v2/pkg/proc" 37 "github.com/telepresenceio/telepresence/v2/pkg/tracing" 38 ) 39 40 func callRecovery(c context.Context, r any, err error) error { 41 if perr := derror.PanicToError(r); perr != nil { 42 dlog.Errorf(c, "%+v", perr) 43 err = perr 44 } 45 return err 46 } 47 48 type reqNumberKey struct{} 49 50 func getReqNumber(ctx context.Context) int64 { 51 num := ctx.Value(reqNumberKey{}) 52 if num == nil { 53 return 0 54 } 55 return num.(int64) 56 } 57 58 func withReqNumber(ctx context.Context, num int64) context.Context { 59 return context.WithValue(ctx, reqNumberKey{}, num) 60 } 61 62 func (s *service) callCtx(ctx context.Context, name string) context.Context { 63 num := atomic.AddInt64(&s.ucn, 1) 64 ctx = withReqNumber(ctx, num) 65 return dgroup.WithGoroutineName(ctx, fmt.Sprintf("/%s-%d", name, num)) 66 } 67 68 func (s *service) logCall(c context.Context, callName string, f func(context.Context)) { 69 c = s.callCtx(c, callName) 70 dlog.Debug(c, "called") 71 defer dlog.Debug(c, "returned") 72 f(c) 73 } 74 75 func (s *service) FuseFTPError() error { 76 return s.fuseFTPError 77 } 78 79 func (s *service) WithSession(c context.Context, callName string, f func(context.Context, userd.Session) error) (err error) { 80 s.logCall(c, callName, func(_ context.Context) { 81 if atomic.LoadInt32(&s.sessionQuitting) != 0 { 82 err = status.Error(codes.Canceled, "session cancelled") 83 return 84 } 85 s.sessionLock.RLock() 86 defer s.sessionLock.RUnlock() 87 if s.session == nil { 88 err = status.Error(codes.Unavailable, "no active session") 89 return 90 } 91 if s.sessionContext.Err() != nil { 92 // Session context has been cancelled 93 err = status.Error(codes.Canceled, "session cancelled") 94 return 95 } 96 defer func() { err = callRecovery(c, recover(), err) }() 97 num := getReqNumber(c) 98 ctx := dgroup.WithGoroutineName(s.sessionContext, fmt.Sprintf("/%s-%d", callName, num)) 99 ctx, span := otel.Tracer("").Start(ctx, callName) 100 defer span.End() 101 err = f(ctx, s.session) 102 }) 103 return 104 } 105 106 func (s *service) Version(_ context.Context, _ *empty.Empty) (*common.VersionInfo, error) { 107 executable, err := client.Executable() 108 if err != nil { 109 return &common.VersionInfo{}, err 110 } 111 return &common.VersionInfo{ 112 ApiVersion: client.APIVersion, 113 Version: client.Version(), 114 Executable: executable, 115 Name: client.DisplayName, 116 }, nil 117 } 118 119 func (s *service) Connect(ctx context.Context, cr *rpc.ConnectRequest) (result *rpc.ConnectInfo, err error) { 120 s.logCall(ctx, "Connect", func(c context.Context) { 121 select { 122 case <-ctx.Done(): 123 err = status.Error(codes.Unavailable, ctx.Err().Error()) 124 return 125 case s.connectRequest <- cr: 126 } 127 128 select { 129 case <-ctx.Done(): 130 err = status.Error(codes.Unavailable, ctx.Err().Error()) 131 case result = <-s.connectResponse: 132 } 133 }) 134 return result, err 135 } 136 137 func (s *service) Disconnect(ctx context.Context, ex *empty.Empty) (*empty.Empty, error) { 138 s.logCall(ctx, "Disconnect", func(ctx context.Context) { 139 s.cancelSession() 140 _ = s.withRootDaemon(ctx, func(ctx context.Context, rd daemon.DaemonClient) error { 141 _, err := rd.Disconnect(ctx, ex) 142 return err 143 }) 144 }) 145 return &empty.Empty{}, nil 146 } 147 148 func (s *service) Status(ctx context.Context, ex *empty.Empty) (result *rpc.ConnectInfo, err error) { 149 s.logCall(ctx, "Status", func(c context.Context) { 150 s.sessionLock.RLock() 151 defer s.sessionLock.RUnlock() 152 if s.session == nil { 153 result = &rpc.ConnectInfo{Error: rpc.ConnectInfo_DISCONNECTED} 154 _ = s.withRootDaemon(c, func(c context.Context, dc daemon.DaemonClient) error { 155 result.DaemonStatus, err = dc.Status(c, ex) 156 return nil 157 }) 158 } else { 159 result = s.session.Status(s.sessionContext) 160 } 161 }) 162 return 163 } 164 165 // isMultiPortIntercept checks if the intercept is one of several active intercepts on the same workload. 166 // If it is, then the first returned value will be true and the second will indicate if those intercepts are 167 // on different services. Otherwise, this function returns false, false. 168 func (s *service) isMultiPortIntercept(spec *manager.InterceptSpec) (multiPort, multiService bool) { 169 wis := s.session.InterceptsForWorkload(spec.Agent, spec.Namespace) 170 171 // The InterceptsForWorkload will not include failing or removed intercepts so the 172 // subject must be added unless it's already there. 173 active := false 174 for _, is := range wis { 175 if is.Name == spec.Name { 176 active = true 177 break 178 } 179 } 180 if !active { 181 wis = append(wis, spec) 182 } 183 if len(wis) < 2 { 184 return false, false 185 } 186 var suid string 187 for _, is := range wis { 188 if suid == "" { 189 suid = is.ServiceUid 190 } else if suid != is.ServiceUid { 191 return true, true 192 } 193 } 194 return true, false 195 } 196 197 func (s *service) scoutInterceptEntries(ctx context.Context, spec *manager.InterceptSpec, result *rpc.InterceptResult) ([]scout.Entry, bool) { 198 // The scout belongs to the session and can only contain session specific meta-data, 199 // so we don't want to use scout.SetMetadatum() here. 200 entries := make([]scout.Entry, 0, 7) 201 if spec != nil { 202 entries = append(entries, 203 scout.Entry{Key: "service_name", Value: spec.ServiceName}, 204 scout.Entry{Key: "service_namespace", Value: spec.Namespace}, 205 scout.Entry{Key: "intercept_mechanism", Value: spec.Mechanism}, 206 scout.Entry{Key: "intercept_mechanism_numargs", Value: len(spec.Mechanism)}, 207 ) 208 multiPort, multiService := s.isMultiPortIntercept(spec) 209 if multiPort { 210 entries = append(entries, scout.Entry{Key: "multi_port", Value: multiPort}) 211 if multiService { 212 entries = append(entries, scout.Entry{Key: "multi_service", Value: multiService}) 213 } 214 } 215 } 216 if result != nil { 217 entries = append(entries, scout.Entry{Key: "workload_kind", Value: result.WorkloadKind}) 218 if result.Error != common.InterceptError_UNSPECIFIED { 219 es := result.Error.String() 220 if result.ErrorText != "" { 221 es = fmt.Sprintf("%s: %s", es, result.ErrorText) 222 } 223 dlog.Debugf(ctx, "reporting error: %s", es) 224 entries = append(entries, scout.Entry{Key: "error", Value: es}) 225 return entries, false 226 } 227 } 228 return entries, true 229 } 230 231 func (s *service) CanIntercept(c context.Context, ir *rpc.CreateInterceptRequest) (result *rpc.InterceptResult, err error) { 232 var entries []scout.Entry 233 ok := false 234 defer func() { 235 var action string 236 if ok { 237 action = "connector_can_intercept_success" 238 } else { 239 action = "connector_can_intercept_fail" 240 } 241 scout.Report(c, action, entries...) 242 }() 243 err = s.WithSession(c, "CanIntercept", func(c context.Context, session userd.Session) error { 244 span := trace.SpanFromContext(c) 245 tracing.RecordInterceptSpec(span, ir.Spec) 246 _, result = session.CanIntercept(c, ir) 247 if result == nil { 248 result = &rpc.InterceptResult{Error: common.InterceptError_UNSPECIFIED} 249 } 250 entries, ok = s.scoutInterceptEntries(c, ir.GetSpec(), result) 251 return nil 252 }) 253 return 254 } 255 256 func (s *service) CreateIntercept(c context.Context, ir *rpc.CreateInterceptRequest) (result *rpc.InterceptResult, err error) { 257 var entries []scout.Entry 258 ok := false 259 defer func() { 260 var action string 261 if ok { 262 action = "connector_create_intercept_success" 263 } else { 264 action = "connector_create_intercept_fail" 265 } 266 scout.Report(c, action, entries...) 267 }() 268 err = s.WithSession(c, "CreateIntercept", func(c context.Context, session userd.Session) error { 269 span := trace.SpanFromContext(c) 270 tracing.RecordInterceptSpec(span, ir.Spec) 271 result = session.AddIntercept(c, ir) 272 if result != nil && result.InterceptInfo != nil { 273 tracing.RecordInterceptInfo(span, result.InterceptInfo) 274 } 275 entries, ok = s.scoutInterceptEntries(c, ir.GetSpec(), result) 276 return nil 277 }) 278 return 279 } 280 281 func (s *service) RemoveIntercept(c context.Context, rr *manager.RemoveInterceptRequest2) (result *rpc.InterceptResult, err error) { 282 var spec *manager.InterceptSpec 283 var entries []scout.Entry 284 ok := false 285 defer func() { 286 var action string 287 if ok { 288 action = "connector_remove_intercept_success" 289 } else { 290 action = "connector_remove_intercept_fail" 291 } 292 scout.Report(c, action, entries...) 293 }() 294 err = s.WithSession(c, "RemoveIntercept", func(c context.Context, session userd.Session) error { 295 result = &rpc.InterceptResult{} 296 spec = session.GetInterceptSpec(rr.Name) 297 if spec != nil { 298 result.ServiceUid = spec.ServiceUid 299 result.WorkloadKind = spec.WorkloadKind 300 } 301 if err := session.RemoveIntercept(c, rr.Name); err != nil { 302 if status.Code(err) == codes.NotFound { 303 result.Error = common.InterceptError_NOT_FOUND 304 result.ErrorText = rr.Name 305 result.ErrorCategory = int32(errcat.User) 306 } else { 307 result.Error = common.InterceptError_TRAFFIC_MANAGER_ERROR 308 result.ErrorText = err.Error() 309 result.ErrorCategory = int32(errcat.Unknown) 310 } 311 } 312 entries, ok = s.scoutInterceptEntries(c, spec, result) 313 return nil 314 }) 315 return result, err 316 } 317 318 func (s *service) UpdateIntercept(c context.Context, rr *manager.UpdateInterceptRequest) (result *manager.InterceptInfo, err error) { 319 err = s.WithSession(c, "UpdateIntercept", func(c context.Context, session userd.Session) error { 320 result, err = session.ManagerClient().UpdateIntercept(c, rr) 321 return err 322 }) 323 return 324 } 325 326 func (s *service) AddInterceptor(ctx context.Context, interceptor *rpc.Interceptor) (*empty.Empty, error) { 327 return &empty.Empty{}, s.WithSession(ctx, "AddInterceptor", func(_ context.Context, session userd.Session) error { 328 return session.AddInterceptor(interceptor.InterceptId, interceptor) 329 }) 330 } 331 332 func (s *service) RemoveInterceptor(ctx context.Context, interceptor *rpc.Interceptor) (*empty.Empty, error) { 333 return &empty.Empty{}, s.WithSession(ctx, "RemoveInterceptor", func(_ context.Context, session userd.Session) error { 334 return session.RemoveInterceptor(interceptor.InterceptId) 335 }) 336 } 337 338 func (s *service) List(c context.Context, lr *rpc.ListRequest) (result *rpc.WorkloadInfoSnapshot, err error) { 339 err = s.WithSession(c, "List", func(c context.Context, session userd.Session) error { 340 result, err = session.WorkloadInfoSnapshot(c, []string{lr.Namespace}, lr.Filter) 341 return err 342 }) 343 return 344 } 345 346 func (s *service) WatchWorkloads(wr *rpc.WatchWorkloadsRequest, stream rpc.Connector_WatchWorkloadsServer) error { 347 var sessionCtx context.Context 348 var session userd.Session 349 350 err := s.WithSession(stream.Context(), "WatchWorkloads", func(c context.Context, s userd.Session) error { 351 session, sessionCtx = s, c 352 return nil 353 }) 354 if err != nil { 355 return nil 356 } 357 358 return session.WatchWorkloads(sessionCtx, wr, stream) 359 } 360 361 func (s *service) Uninstall(c context.Context, ur *rpc.UninstallRequest) (result *common.Result, err error) { 362 err = s.WithSession(c, "Uninstall", func(c context.Context, session userd.Session) error { 363 result, err = session.Uninstall(c, ur) 364 return err 365 }) 366 return 367 } 368 369 func (s *service) GetConfig(ctx context.Context, empty *empty.Empty) (cfg *rpc.ClientConfig, err error) { 370 err = s.WithSession(ctx, "GetConfig", func(c context.Context, session userd.Session) error { 371 sc, err := session.GetConfig(ctx) 372 if err != nil { 373 return err 374 } 375 data, err := json.Marshal(sc) 376 if err != nil { 377 return status.Error(codes.Internal, err.Error()) 378 } 379 cfg = &rpc.ClientConfig{Json: data} 380 return nil 381 }) 382 return 383 } 384 385 func (s *service) GatherLogs(ctx context.Context, request *rpc.LogsRequest) (result *rpc.LogsResponse, err error) { 386 err = s.WithSession(ctx, "GatherLogs", func(c context.Context, session userd.Session) error { 387 result, err = session.GatherLogs(c, request) 388 return err 389 }) 390 return 391 } 392 393 func (s *service) SetLogLevel(ctx context.Context, request *rpc.LogLevelRequest) (result *empty.Empty, err error) { 394 s.logCall(ctx, "SetLogLevel", func(c context.Context) { 395 mrq := &manager.LogLevelRequest{ 396 LogLevel: request.LogLevel, 397 Duration: request.Duration, 398 } 399 setLocal := func() { 400 duration := time.Duration(0) 401 if request.Duration != nil { 402 duration = request.Duration.AsDuration() 403 } 404 if err = logging.SetAndStoreTimedLevel(ctx, s.timedLogLevel, request.LogLevel, duration, userd.ProcessName); err != nil { 405 err = status.Error(codes.Internal, err.Error()) 406 } else if !s.rootSessionInProc { 407 err = s.withRootDaemon(ctx, func(ctx context.Context, rd daemon.DaemonClient) error { 408 _, err := rd.SetLogLevel(ctx, mrq) 409 return err 410 }) 411 } 412 } 413 setRemote := func() { 414 err = s.WithSession(ctx, "SetLogLevel", func(ctx context.Context, session userd.Session) error { 415 _, err := session.ManagerClient().SetLogLevel(ctx, mrq) 416 return err 417 }) 418 } 419 switch request.Scope { 420 case rpc.LogLevelRequest_LOCAL_ONLY: 421 setLocal() 422 case rpc.LogLevelRequest_REMOTE_ONLY: 423 setRemote() 424 default: 425 setLocal() 426 if err == nil { 427 setRemote() 428 } 429 } 430 }) 431 return &empty.Empty{}, err 432 } 433 434 func (s *service) Quit(ctx context.Context, ex *empty.Empty) (*empty.Empty, error) { 435 s.logCall(ctx, "Quit", func(c context.Context) { 436 s.sessionLock.RLock() 437 defer s.sessionLock.RUnlock() 438 s.cancelSessionReadLocked() 439 s.quit() 440 _ = s.withRootDaemon(ctx, func(ctx context.Context, rd daemon.DaemonClient) error { 441 _, err := rd.Quit(ctx, ex) 442 return err 443 }) 444 }) 445 return ex, nil 446 } 447 448 func (s *service) RemoteMountAvailability(ctx context.Context, _ *empty.Empty) (*common.Result, error) { 449 if proc.RunningInContainer() { 450 // We mount using docker volumes and the telemount driver plugin. 451 return errcat.ToResult(nil), nil 452 } 453 if client.GetConfig(ctx).Intercept().UseFtp { 454 return errcat.ToResult(s.FuseFTPError()), nil 455 } 456 457 // Use CombinedOutput to include stderr which has information about whether they 458 // need to upgrade to a newer version of macFUSE or not 459 var cmd *dexec.Cmd 460 if runtime.GOOS == "windows" { 461 cmd = proc.CommandContext(ctx, "sshfs-win", "cmd", "-V") 462 } else { 463 cmd = proc.CommandContext(ctx, "sshfs", "-V") 464 } 465 cmd.DisableLogging = true 466 out, err := cmd.CombinedOutput() 467 if err != nil { 468 dlog.Errorf(ctx, "sshfs not installed: %v", err) 469 return errcat.ToResult(errors.New("sshfs is not installed on your local machine")), nil 470 } 471 472 // OSXFUSE changed to macFUSE, and we've noticed that older versions of OSXFUSE 473 // can cause browsers to hang + kernel crashes, so we add an error to prevent 474 // our users from running into this problem. 475 // OSXFUSE isn't included in the output of sshfs -V in versions of 4.0.0 so 476 // we check for that as a proxy for if they have the right version or not. 477 if bytes.Contains(out, []byte("OSXFUSE")) { 478 return errcat.ToResult(errors.New(`macFUSE 4.0.5 or higher is required on your local machine`)), nil 479 } 480 return errcat.ToResult(nil), nil 481 } 482 483 func (s *service) GetNamespaces(ctx context.Context, req *rpc.GetNamespacesRequest) (*rpc.GetNamespacesResponse, error) { 484 var resp rpc.GetNamespacesResponse 485 err := s.WithSession(ctx, "GetNamespaces", func(ctx context.Context, session userd.Session) error { 486 resp.Namespaces = session.GetCurrentNamespaces(req.ForClientAccess) 487 return nil 488 }) 489 if err != nil { 490 return nil, err 491 } 492 493 if p := req.Prefix; p != "" { 494 var namespaces []string 495 for _, namespace := range resp.Namespaces { 496 if strings.HasPrefix(namespace, p) { 497 namespaces = append(namespaces, namespace) 498 } 499 } 500 resp.Namespaces = namespaces 501 } 502 503 return &resp, nil 504 } 505 506 func (s *service) GatherTraces(ctx context.Context, request *rpc.TracesRequest) (result *common.Result, err error) { 507 err = s.WithSession(ctx, "GatherTraces", func(ctx context.Context, session userd.Session) error { 508 result = session.GatherTraces(ctx, request) 509 return nil 510 }) 511 return 512 } 513 514 func (s *service) TrafficManagerVersion(ctx context.Context, _ *empty.Empty) (vi *common.VersionInfo, err error) { 515 err = s.WithSession(ctx, "TrafficManagerVersion", func(ctx context.Context, session userd.Session) error { 516 vi = &common.VersionInfo{Name: session.ManagerName(), Version: "v" + session.ManagerVersion().String()} 517 return nil 518 }) 519 return 520 } 521 522 func (s *service) RootDaemonVersion(ctx context.Context, empty *empty.Empty) (vi *common.VersionInfo, err error) { 523 err = s.withRootDaemon(ctx, func(ctx context.Context, rd daemon.DaemonClient) error { 524 vi, err = rd.Version(ctx, empty) 525 return err 526 }) 527 return vi, err 528 } 529 530 func (s *service) AgentImageFQN(ctx context.Context, empty *emptypb.Empty) (fqn *manager.AgentImageFQN, err error) { 531 err = s.WithSession(ctx, "AgentImageFQN", func(ctx context.Context, session userd.Session) error { 532 fqn, err = session.ManagerClient().GetAgentImageFQN(ctx, empty) 533 return err 534 }) 535 return fqn, err 536 } 537 538 func (s *service) GetClusterSubnets(ctx context.Context, _ *empty.Empty) (cs *rpc.ClusterSubnets, err error) { 539 podSubnets := []*manager.IPNet{} 540 svcSubnets := []*manager.IPNet{} 541 err = s.WithSession(ctx, "GetClusterSubnets", func(ctx context.Context, session userd.Session) error { 542 // The manager can sometimes send the different subnets in different Sends, 543 // but after 5 seconds of listening to it, we should expect to have everything 544 tCtx, tCancel := context.WithTimeout(ctx, 5*time.Second) 545 defer tCancel() 546 infoStream, err := session.ManagerClient().WatchClusterInfo(tCtx, session.SessionInfo()) 547 if err != nil { 548 return err 549 } 550 for { 551 mgrInfo, err := infoStream.Recv() 552 if err != nil { 553 if tCtx.Err() != nil || errors.Is(err, io.EOF) { 554 err = nil 555 } 556 return err 557 } 558 if mgrInfo.ServiceSubnet != nil { 559 svcSubnets = append(svcSubnets, mgrInfo.ServiceSubnet) 560 } 561 podSubnets = append(podSubnets, mgrInfo.PodSubnets...) 562 } 563 }) 564 if err != nil { 565 return nil, err 566 } 567 return &rpc.ClusterSubnets{PodSubnets: podSubnets, SvcSubnets: svcSubnets}, nil 568 } 569 570 func (s *service) GetIntercept(ctx context.Context, request *manager.GetInterceptRequest) (ii *manager.InterceptInfo, err error) { 571 err = s.WithSession(ctx, "GetIntercept", func(ctx context.Context, session userd.Session) error { 572 ii = session.GetInterceptInfo(request.Name) 573 if ii == nil { 574 return status.Errorf(codes.NotFound, "found no intercept named %s", request.Name) 575 } 576 return nil 577 }) 578 return ii, err 579 } 580 581 func (s *service) SetDNSExcludes(ctx context.Context, req *daemon.SetDNSExcludesRequest) (*emptypb.Empty, error) { 582 err := s.WithSession(ctx, "SetDNSExcludes", func(ctx context.Context, session userd.Session) error { 583 _, err := session.RootDaemon().SetDNSExcludes(ctx, req) 584 return err 585 }) 586 return &empty.Empty{}, err 587 } 588 589 func (s *service) SetDNSMappings(ctx context.Context, req *daemon.SetDNSMappingsRequest) (*emptypb.Empty, error) { 590 err := s.WithSession(ctx, "SetDNSMappings", func(ctx context.Context, session userd.Session) error { 591 _, err := session.RootDaemon().SetDNSMappings(ctx, req) 592 return err 593 }) 594 return &empty.Empty{}, err 595 } 596 597 func (s *service) withRootDaemon(ctx context.Context, f func(ctx context.Context, daemonClient daemon.DaemonClient) error) error { 598 if s.rootSessionInProc { 599 return status.Error(codes.Unavailable, "root daemon is embedded") 600 } 601 conn, err := socket.Dial(ctx, socket.RootDaemonPath(ctx)) 602 if err == nil { 603 defer conn.Close() 604 err = f(ctx, daemon.NewDaemonClient(conn)) 605 } 606 if err != nil { 607 err = status.Errorf(status.Code(err), "root daemon: %s", err.Error()) 608 } 609 return err 610 }