github.com/cilium/cilium@v1.16.2/pkg/hubble/relay/pool/client.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package pool
     5  
     6  import (
     7  	"context"
     8  	"crypto/tls"
     9  	"fmt"
    10  	"net"
    11  
    12  	"google.golang.org/grpc"
    13  	"google.golang.org/grpc/credentials"
    14  	"google.golang.org/grpc/credentials/insecure"
    15  
    16  	"github.com/cilium/cilium/pkg/crypto/certloader"
    17  	poolTypes "github.com/cilium/cilium/pkg/hubble/relay/pool/types"
    18  	hubbleopts "github.com/cilium/cilium/pkg/hubble/server/serveroption"
    19  	"github.com/cilium/cilium/pkg/lock"
    20  	"github.com/cilium/cilium/pkg/time"
    21  )
    22  
    23  // GRPCClientConnBuilder is a generic ClientConnBuilder implementation.
    24  type GRPCClientConnBuilder struct {
    25  	// DialTimeout specifies the timeout used when establishing a new
    26  	// connection.
    27  	DialTimeout time.Duration
    28  	// Options is a set of grpc.DialOption to be used when creating a new
    29  	// connection.
    30  	Options []grpc.DialOption
    31  
    32  	// TLSConfig is used to build transport credentials for the connection.
    33  	// If not provided, insecure credentials are added to Options before creating
    34  	// a new ClientConn.
    35  	TLSConfig certloader.ClientConfigBuilder
    36  }
    37  
    38  // ClientConn implements ClientConnBuilder.ClientConn.
    39  func (b GRPCClientConnBuilder) ClientConn(target, hostname string) (poolTypes.ClientConn, error) {
    40  	// Ensure that the hostname (used as ServerName) information is given when
    41  	// Relay is configured with mTLS, and empty otherwise. We do this to report
    42  	// a mTLS misconfiguration between Hubble and Relay as early as possible.
    43  	switch {
    44  	case b.TLSConfig != nil && hostname == "":
    45  		return nil, fmt.Errorf("missing TLS ServerName for %s", target)
    46  	case b.TLSConfig == nil && hostname != "":
    47  		return nil, fmt.Errorf("unexpected TLS ServerName %s for %s", hostname, target)
    48  	}
    49  
    50  	ctx, cancel := context.WithTimeout(context.Background(), b.DialTimeout)
    51  	defer cancel()
    52  	opts := make([]grpc.DialOption, len(b.Options))
    53  	copy(opts, b.Options)
    54  
    55  	if b.TLSConfig == nil {
    56  		opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials()))
    57  	} else {
    58  		// NOTE: gosec is unable to resolve the constant and warns about "TLS
    59  		// MinVersion too low".
    60  		baseConf := &tls.Config{ //nolint:gosec
    61  			ServerName: hostname,
    62  			MinVersion: hubbleopts.MinTLSVersion,
    63  		}
    64  		opts = append(opts, grpc.WithTransportCredentials(
    65  			&grpcTLSCredentialsWrapper{
    66  				TransportCredentials: credentials.NewTLS(b.TLSConfig.ClientConfig(baseConf)),
    67  				baseConf:             baseConf,
    68  				TLSConfig:            b.TLSConfig,
    69  			},
    70  		))
    71  	}
    72  	return grpc.DialContext(ctx, target, opts...)
    73  }
    74  
    75  var _ credentials.TransportCredentials = &grpcTLSCredentialsWrapper{}
    76  
    77  // grpcTLSCredentialsWrapper wraps gRPC TransportCredentials and fetches the
    78  // newest TLS configuration from certloader whenever we a new TLS connection
    79  // is established.
    80  //
    81  // A gRPC ClientConn will call ClientHandshake whenever it tries to establish
    82  // a new TLS connection. This happens in the beginning when dialing, but also
    83  // when the connection is lost and gRPC tries to reestablish the connection.
    84  // Wrapping the ClientHandshake and fetching the updated certificate and CA,
    85  // allows us to transparently reload certificates when they change, without
    86  // closing and redialing the gRPC ClientConn.
    87  type grpcTLSCredentialsWrapper struct {
    88  	credentials.TransportCredentials
    89  
    90  	mu        lock.Mutex
    91  	baseConf  *tls.Config
    92  	TLSConfig certloader.ClientConfigBuilder
    93  }
    94  
    95  // ClientHandshake implements credentials.TransportCredentials.
    96  func (w *grpcTLSCredentialsWrapper) ClientHandshake(ctx context.Context, addr string, conn net.Conn) (net.Conn, credentials.AuthInfo, error) {
    97  	w.mu.Lock()
    98  	defer w.mu.Unlock()
    99  	w.TransportCredentials = credentials.NewTLS(w.TLSConfig.ClientConfig(w.baseConf))
   100  	return w.TransportCredentials.ClientHandshake(ctx, addr, conn)
   101  }
   102  
   103  // Clone implements credentials.TransportCredentials.
   104  func (w *grpcTLSCredentialsWrapper) Clone() credentials.TransportCredentials {
   105  	w.mu.Lock()
   106  	defer w.mu.Unlock()
   107  	return &grpcTLSCredentialsWrapper{
   108  		baseConf:             w.baseConf.Clone(),
   109  		TransportCredentials: w.TransportCredentials.Clone(),
   110  		TLSConfig:            w.TLSConfig,
   111  	}
   112  }