github.com/safedep/dry@v0.0.0-20241016050132-a15651f0548b/adapters/grpc/grpc.go (about) 1 package grpc 2 3 import ( 4 "context" 5 "crypto/tls" 6 "fmt" 7 "net" 8 "net/http" 9 "os" 10 "time" 11 12 "github.com/safedep/dry/adapters" 13 "github.com/safedep/dry/log" 14 "github.com/safedep/dry/retry" 15 "google.golang.org/grpc" 16 "google.golang.org/grpc/credentials" 17 "google.golang.org/grpc/credentials/insecure" 18 19 grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware" 20 grpc_validator "github.com/grpc-ecosystem/go-grpc-middleware/validator" 21 22 "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" 23 grpcotel "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" 24 ) 25 26 type GrpcAdapterConfigurer func(server *grpc.Server) 27 type GrpcClientConfigurer func(conn *grpc.ClientConn) 28 29 var ( 30 NoGrpcDialOptions = []grpc.DialOption{} 31 NoGrpcConfigurer = func(conn *grpc.ClientConn) {} 32 ) 33 34 func StartGrpcMtlsServer(name, serverName, host, port string, 35 sopts []grpc.ServerOption, configure GrpcAdapterConfigurer) { 36 tc, err := adapters.TlsConfigFromEnvironment(serverName) 37 if err != nil { 38 log.Fatalf("Failed to setup TLS from environment: %v", err) 39 } 40 41 creds := credentials.NewTLS(&tc) 42 sopts = append(sopts, grpc.Creds(creds)) 43 44 StartGrpcServer(name, host, port, sopts, configure) 45 } 46 47 func StartGrpcServer(name, host, port string, sopts []grpc.ServerOption, 48 configure GrpcAdapterConfigurer) { 49 addr := net.JoinHostPort(host, port) 50 listener, err := net.Listen("tcp", addr) 51 52 if err != nil { 53 log.Fatalf("Failed to listen on %s:%s - %s", host, port, err.Error()) 54 } 55 56 sopts = append(sopts, grpc.UnaryInterceptor( 57 grpc_middleware.ChainUnaryServer( 58 grpcotel.UnaryServerInterceptor(), 59 grpc_validator.UnaryServerInterceptor(), 60 ), 61 )) 62 63 sopts = append(sopts, grpc.StreamInterceptor( 64 grpc_middleware.ChainStreamServer( 65 grpcotel.StreamServerInterceptor(), 66 grpc_validator.StreamServerInterceptor(), 67 ), 68 )) 69 70 server := grpc.NewServer(sopts...) 71 configure(server) 72 73 log.Infof("Starting %s gRPC server on %s:%s", name, host, port) 74 err = server.Serve(listener) 75 76 log.Errorf("gRPC Server exit: %s", err.Error()) 77 } 78 79 func GrpcMtlsClient(name, serverName, host, port string, dopts []grpc.DialOption, 80 configurer GrpcClientConfigurer) (*grpc.ClientConn, error) { 81 tc, err := grpcTransportCredentials(serverName) 82 if err != nil { 83 return nil, fmt.Errorf("failed to setup client transport credentials: %w", err) 84 } 85 86 dopts = append(dopts, tc) 87 return grpcClient(name, host, port, dopts, configurer) 88 } 89 90 type tokenCredential struct { 91 token string 92 headers http.Header 93 requireTransportSecurity bool 94 } 95 96 func (t *tokenCredential) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) { 97 h := map[string]string{} 98 for k, v := range t.headers { 99 if len(v) > 0 && v[0] != "" { 100 h[k] = v[0] 101 } 102 } 103 104 if t.token != "" { 105 h["authorization"] = t.token 106 } 107 108 return h, nil 109 } 110 111 func (t *tokenCredential) RequireTransportSecurity() bool { 112 return t.requireTransportSecurity 113 } 114 115 func GrpcClient(name, host, port string, token string, headers http.Header, 116 dopts []grpc.DialOption, configurer ...GrpcClientConfigurer) (*grpc.ClientConn, error) { 117 if os.Getenv("INSECURE_GRPC_CLIENT_USE_INSECURE_TRANSPORT") == "true" { 118 return GrpcInsecureClient(name, host, port, token, headers, dopts, NoGrpcConfigurer) 119 } else { 120 return GrpcSecureClient(name, host, port, token, headers, dopts, configurer...) 121 } 122 } 123 124 func GrpcInsecureClient(name, host, port string, token string, headers http.Header, 125 dopts []grpc.DialOption, configurer GrpcClientConfigurer) (*grpc.ClientConn, error) { 126 tc := grpc.WithTransportCredentials(insecure.NewCredentials()) 127 dopts = append(dopts, tc) 128 dopts = append(dopts, grpc.WithPerRPCCredentials(&tokenCredential{ 129 token: token, 130 headers: headers, 131 requireTransportSecurity: false, 132 })) 133 134 return grpcClient(name, host, port, dopts, configurer) 135 } 136 137 func GrpcSecureClient(name, host, port string, token string, headers http.Header, 138 dopts []grpc.DialOption, configurer ...GrpcClientConfigurer) (*grpc.ClientConn, error) { 139 creds := []grpc.DialOption{} 140 creds = append(creds, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{}))) 141 creds = append(creds, grpc.WithPerRPCCredentials(&tokenCredential{ 142 token: token, 143 headers: headers, 144 requireTransportSecurity: true, 145 })) 146 147 dopts = append(dopts, creds...) 148 return grpcClient(name, host, port, dopts, configurer...) 149 } 150 151 func grpcClient(name, host, port string, dopts []grpc.DialOption, configurer ...GrpcClientConfigurer) (*grpc.ClientConn, error) { 152 log.Infof("[%s] Connecting to gRPC server %s:%s", name, host, port) 153 154 dopts = append(dopts, grpc.WithUnaryInterceptor(otelgrpc.UnaryClientInterceptor())) 155 dopts = append(dopts, grpc.WithStreamInterceptor(otelgrpc.StreamClientInterceptor())) 156 157 var conn *grpc.ClientConn 158 var err error 159 160 retry.InvokeWithRetry(retry.RetryConfig{ 161 Count: 10, 162 Sleep: 1 * time.Second, 163 }, func(arg retry.RetryFuncArg) error { 164 conn, err = grpc.Dial(net.JoinHostPort(host, port), dopts...) 165 if err != nil { 166 log.Infof("[%s] Failed to connect to gRPC server %d/%d : %v", 167 name, arg.Current, arg.Total, err) 168 } 169 170 return err 171 }) 172 173 if err != nil { 174 return nil, err 175 } 176 177 for _, c := range configurer { 178 c(conn) 179 } 180 181 return conn, nil 182 } 183 184 func grpcTransportCredentials(serverName string) (grpc.DialOption, error) { 185 tlsConfig, err := adapters.TlsConfigFromEnvironment(serverName) 186 if err != nil { 187 return nil, err 188 } 189 190 creds := credentials.NewTLS(&tlsConfig) 191 return grpc.WithTransportCredentials(creds), nil 192 }