github.com/argoproj/argo-cd/v3@v3.2.1/util/grpc/grpc.go (about) 1 package grpc 2 3 import ( 4 "context" 5 "crypto/tls" 6 "fmt" 7 "net" 8 "runtime/debug" 9 "strings" 10 "time" 11 12 "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/recovery" 13 "github.com/sirupsen/logrus" 14 "golang.org/x/net/proxy" 15 "google.golang.org/grpc" 16 "google.golang.org/grpc/codes" 17 "google.golang.org/grpc/connectivity" 18 "google.golang.org/grpc/credentials" 19 "google.golang.org/grpc/credentials/insecure" 20 "google.golang.org/grpc/keepalive" 21 "google.golang.org/grpc/status" 22 23 "github.com/argoproj/argo-cd/v3/common" 24 ) 25 26 // LoggerRecoveryHandler return a handler for recovering from panics and returning error 27 func LoggerRecoveryHandler(log *logrus.Entry) recovery.RecoveryHandlerFunc { 28 return func(p any) (err error) { 29 log.Errorf("Recovered from panic: %+v\n%s", p, debug.Stack()) 30 return status.Errorf(codes.Internal, "%s", p) 31 } 32 } 33 34 // BlockingNewClient is a helper method to dial the given address, using optional TLS credentials, 35 // and blocking until the returned connection is ready. If the given credentials are nil, the 36 // connection will be insecure (plain-text). 37 // Lifted from: https://github.com/fullstorydev/grpcurl/blob/master/grpcurl.go 38 func BlockingNewClient(ctx context.Context, network, address string, creds credentials.TransportCredentials, opts ...grpc.DialOption) (*grpc.ClientConn, error) { 39 proxyDialer := proxy.FromEnvironment() 40 rawConn, err := proxyDialer.Dial(network, address) 41 if err != nil { 42 return nil, fmt.Errorf("error dial proxy: %w", err) 43 } 44 45 if creds != nil { 46 rawConn, _, err = creds.ClientHandshake(ctx, address, rawConn) 47 if err != nil { 48 return nil, fmt.Errorf("error creating connection: %w", err) 49 } 50 } 51 customDialer := func(_ context.Context, _ string) (net.Conn, error) { 52 return rawConn, nil 53 } 54 55 opts = append(opts, 56 grpc.WithContextDialer(customDialer), 57 grpc.WithTransportCredentials(insecure.NewCredentials()), 58 grpc.WithKeepaliveParams(keepalive.ClientParameters{Time: common.GetGRPCKeepAliveTime()}), 59 ) 60 61 conn, err := grpc.NewClient("passthrough:"+address, opts...) 62 if err != nil { 63 return nil, fmt.Errorf("grpc.NewClient failed: %w", err) 64 } 65 66 conn.Connect() 67 if err := waitForReady(ctx, conn); err != nil { 68 return nil, fmt.Errorf("gRPC connection not ready: %w", err) 69 } 70 71 return conn, nil 72 } 73 74 func waitForReady(ctx context.Context, conn *grpc.ClientConn) error { 75 for { 76 state := conn.GetState() 77 if state == connectivity.Ready { 78 return nil 79 } 80 if !conn.WaitForStateChange(ctx, state) { 81 return ctx.Err() // context timeout or cancellation 82 } 83 } 84 } 85 86 type TLSTestResult struct { 87 TLS bool 88 InsecureErr error 89 } 90 91 func TestTLS(address string, dialTime time.Duration) (*TLSTestResult, error) { 92 if parts := strings.Split(address, ":"); len(parts) == 1 { 93 // If port is unspecified, assume the most likely port 94 address += ":443" 95 } 96 var testResult TLSTestResult 97 var tlsConfig tls.Config 98 tlsConfig.InsecureSkipVerify = true 99 creds := credentials.NewTLS(&tlsConfig) 100 101 // Set timeout when dialing to the server 102 // fix: https://github.com/argoproj/argo-cd/issues/9679 103 ctx, cancel := context.WithTimeout(context.Background(), dialTime) 104 defer cancel() 105 106 conn, err := BlockingNewClient(ctx, "tcp", address, creds) 107 if err == nil { 108 _ = conn.Close() 109 testResult.TLS = true 110 creds := credentials.NewTLS(&tls.Config{}) 111 ctx, cancel := context.WithTimeout(context.Background(), dialTime) 112 defer cancel() 113 114 conn, err := BlockingNewClient(ctx, "tcp", address, creds) 115 if err == nil { 116 _ = conn.Close() 117 } else { 118 // if connection was successful with InsecureSkipVerify true, but unsuccessful with 119 // InsecureSkipVerify false, it means server is not configured securely 120 testResult.InsecureErr = err 121 } 122 return &testResult, nil 123 } 124 // If we get here, we were unable to connect via TLS (even with InsecureSkipVerify: true) 125 // It may be because server is running without TLS, or because of real issues (e.g. connection 126 // refused). Test if server accepts plain-text connections 127 ctx, cancel = context.WithTimeout(context.Background(), dialTime) 128 defer cancel() 129 conn, err = BlockingNewClient(ctx, "tcp", address, nil) 130 if err == nil { 131 _ = conn.Close() 132 testResult.TLS = false 133 return &testResult, nil 134 } 135 return nil, err 136 }