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  }