go.etcd.io/etcd@v3.3.27+incompatible/clientv3/credentials/credentials.go (about)

     1  // Copyright 2019 The etcd Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Package credentials implements gRPC credential interface with etcd specific logic.
    16  // e.g., client handshake with custom authority parameter
    17  package credentials
    18  
    19  import (
    20  	"context"
    21  	"crypto/tls"
    22  	"net"
    23  	"sync"
    24  
    25  	"github.com/coreos/etcd/clientv3/balancer/resolver/endpoint"
    26  	"github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes"
    27  	grpccredentials "google.golang.org/grpc/credentials"
    28  )
    29  
    30  // Config defines gRPC credential configuration.
    31  type Config struct {
    32  	TLSConfig *tls.Config
    33  }
    34  
    35  // Bundle defines gRPC credential interface.
    36  type Bundle interface {
    37  	grpccredentials.Bundle
    38  	UpdateAuthToken(token string)
    39  }
    40  
    41  // NewBundle constructs a new gRPC credential bundle.
    42  func NewBundle(cfg Config) Bundle {
    43  	return &bundle{
    44  		tc: newTransportCredential(cfg.TLSConfig),
    45  		rc: newPerRPCCredential(),
    46  	}
    47  }
    48  
    49  // bundle implements "grpccredentials.Bundle" interface.
    50  type bundle struct {
    51  	tc *transportCredential
    52  	rc *perRPCCredential
    53  }
    54  
    55  func (b *bundle) TransportCredentials() grpccredentials.TransportCredentials {
    56  	return b.tc
    57  }
    58  
    59  func (b *bundle) PerRPCCredentials() grpccredentials.PerRPCCredentials {
    60  	return b.rc
    61  }
    62  
    63  func (b *bundle) NewWithMode(mode string) (grpccredentials.Bundle, error) {
    64  	// no-op
    65  	return nil, nil
    66  }
    67  
    68  // transportCredential implements "grpccredentials.TransportCredentials" interface.
    69  // transportCredential wraps TransportCredentials to track which
    70  // addresses are dialed for which endpoints, and then sets the authority when checking the endpoint's cert to the
    71  // hostname or IP of the dialed endpoint.
    72  // This is a workaround of a gRPC load balancer issue. gRPC uses the dialed target's service name as the authority when
    73  // checking all endpoint certs, which does not work for etcd servers using their hostname or IP as the Subject Alternative Name
    74  // in their TLS certs.
    75  // To enable, include both WithTransportCredentials(creds) and WithContextDialer(creds.Dialer)
    76  // when dialing.
    77  type transportCredential struct {
    78  	gtc grpccredentials.TransportCredentials
    79  	mu  sync.Mutex
    80  	// addrToEndpoint maps from the connection addresses that are dialed to the hostname or IP of the
    81  	// endpoint provided to the dialer when dialing
    82  	addrToEndpoint map[string]string
    83  }
    84  
    85  func newTransportCredential(cfg *tls.Config) *transportCredential {
    86  	return &transportCredential{
    87  		gtc:            grpccredentials.NewTLS(cfg),
    88  		addrToEndpoint: map[string]string{},
    89  	}
    90  }
    91  
    92  func (tc *transportCredential) ClientHandshake(ctx context.Context, authority string, rawConn net.Conn) (net.Conn, grpccredentials.AuthInfo, error) {
    93  	// Set the authority when checking the endpoint's cert to the hostname or IP of the dialed endpoint
    94  	tc.mu.Lock()
    95  	dialEp, ok := tc.addrToEndpoint[rawConn.RemoteAddr().String()]
    96  	tc.mu.Unlock()
    97  	if ok {
    98  		_, host, _ := endpoint.ParseEndpoint(dialEp)
    99  		authority = host
   100  	}
   101  	return tc.gtc.ClientHandshake(ctx, authority, rawConn)
   102  }
   103  
   104  // return true if given string is an IP.
   105  func isIP(ep string) bool {
   106  	return net.ParseIP(ep) != nil
   107  }
   108  
   109  func (tc *transportCredential) ServerHandshake(rawConn net.Conn) (net.Conn, grpccredentials.AuthInfo, error) {
   110  	return tc.gtc.ServerHandshake(rawConn)
   111  }
   112  
   113  func (tc *transportCredential) Info() grpccredentials.ProtocolInfo {
   114  	return tc.gtc.Info()
   115  }
   116  
   117  func (tc *transportCredential) Clone() grpccredentials.TransportCredentials {
   118  	copy := map[string]string{}
   119  	tc.mu.Lock()
   120  	for k, v := range tc.addrToEndpoint {
   121  		copy[k] = v
   122  	}
   123  	tc.mu.Unlock()
   124  	return &transportCredential{
   125  		gtc:            tc.gtc.Clone(),
   126  		addrToEndpoint: copy,
   127  	}
   128  }
   129  
   130  func (tc *transportCredential) OverrideServerName(serverNameOverride string) error {
   131  	return tc.gtc.OverrideServerName(serverNameOverride)
   132  }
   133  
   134  func (tc *transportCredential) Dialer(ctx context.Context, dialEp string) (net.Conn, error) {
   135  	// Keep track of which addresses are dialed for which endpoints
   136  	conn, err := endpoint.Dialer(ctx, dialEp)
   137  	if conn != nil {
   138  		tc.mu.Lock()
   139  		tc.addrToEndpoint[conn.RemoteAddr().String()] = dialEp
   140  		tc.mu.Unlock()
   141  	}
   142  	return conn, err
   143  }
   144  
   145  // perRPCCredential implements "grpccredentials.PerRPCCredentials" interface.
   146  type perRPCCredential struct {
   147  	authToken   string
   148  	authTokenMu sync.RWMutex
   149  }
   150  
   151  func newPerRPCCredential() *perRPCCredential { return &perRPCCredential{} }
   152  
   153  func (rc *perRPCCredential) RequireTransportSecurity() bool { return false }
   154  
   155  func (rc *perRPCCredential) GetRequestMetadata(ctx context.Context, s ...string) (map[string]string, error) {
   156  	rc.authTokenMu.RLock()
   157  	authToken := rc.authToken
   158  	rc.authTokenMu.RUnlock()
   159  	return map[string]string{rpctypes.TokenFieldNameGRPC: authToken}, nil
   160  }
   161  
   162  func (b *bundle) UpdateAuthToken(token string) {
   163  	if b.rc == nil {
   164  		return
   165  	}
   166  	b.rc.UpdateAuthToken(token)
   167  }
   168  
   169  func (rc *perRPCCredential) UpdateAuthToken(token string) {
   170  	rc.authTokenMu.Lock()
   171  	rc.authToken = token
   172  	rc.authTokenMu.Unlock()
   173  }