github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/security/auth.go (about)

     1  // Copyright 2015 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  package security
    12  
    13  import (
    14  	"crypto/tls"
    15  	"crypto/x509"
    16  	"strings"
    17  
    18  	"github.com/cockroachdb/cockroach/pkg/util/syncutil"
    19  	"github.com/cockroachdb/errors"
    20  )
    21  
    22  const (
    23  	// NodeUser is used by nodes for intra-cluster traffic.
    24  	NodeUser = "node"
    25  	// RootUser is the default cluster administrator.
    26  	RootUser = "root"
    27  )
    28  
    29  var certPrincipalMap struct {
    30  	syncutil.RWMutex
    31  	m map[string]string
    32  }
    33  
    34  // UserAuthHook authenticates a user based on their username and whether their
    35  // connection originates from a client or another node in the cluster. It
    36  // returns an optional func that is run at connection close.
    37  type UserAuthHook func(string, bool) (connClose func(), _ error)
    38  
    39  // SetCertPrincipalMap sets the global principal map. Each entry in the mapping
    40  // list must either be empty or have the format <source>:<dest>. The principal
    41  // map is used to transform principal names found in the Subject.CommonName or
    42  // DNS-type SubjectAlternateNames fields of certificates.
    43  func SetCertPrincipalMap(mappings []string) error {
    44  	m := make(map[string]string, len(mappings))
    45  	for _, v := range mappings {
    46  		if v == "" {
    47  			continue
    48  		}
    49  		parts := strings.Split(v, ":")
    50  		if len(parts) != 2 {
    51  			return errors.Errorf("invalid <cert-principal>:<db-principal> mapping: %q", v)
    52  		}
    53  		m[parts[0]] = parts[1]
    54  	}
    55  	certPrincipalMap.Lock()
    56  	certPrincipalMap.m = m
    57  	certPrincipalMap.Unlock()
    58  	return nil
    59  }
    60  
    61  func transformPrincipal(commonName string) string {
    62  	certPrincipalMap.RLock()
    63  	mappedName, ok := certPrincipalMap.m[commonName]
    64  	certPrincipalMap.RUnlock()
    65  	if !ok {
    66  		return commonName
    67  	}
    68  	return mappedName
    69  }
    70  
    71  func getCertificatePrincipals(cert *x509.Certificate) []string {
    72  	results := make([]string, 0, 1+len(cert.DNSNames))
    73  	results = append(results, transformPrincipal(cert.Subject.CommonName))
    74  	for _, name := range cert.DNSNames {
    75  		results = append(results, transformPrincipal(name))
    76  	}
    77  	return results
    78  }
    79  
    80  // GetCertificateUsers extract the users from a client certificate.
    81  func GetCertificateUsers(tlsState *tls.ConnectionState) ([]string, error) {
    82  	if tlsState == nil {
    83  		return nil, errors.Errorf("request is not using TLS")
    84  	}
    85  	if len(tlsState.PeerCertificates) == 0 {
    86  		return nil, errors.Errorf("no client certificates in request")
    87  	}
    88  	// The go server handshake code verifies the first certificate, using
    89  	// any following certificates as intermediates. See:
    90  	// https://github.com/golang/go/blob/go1.8.1/src/crypto/tls/handshake_server.go#L723:L742
    91  	peerCert := tlsState.PeerCertificates[0]
    92  	return getCertificatePrincipals(peerCert), nil
    93  }
    94  
    95  // ContainsUser returns true if the specified user is present in the list of
    96  // users.
    97  func ContainsUser(user string, users []string) bool {
    98  	for i := range users {
    99  		if user == users[i] {
   100  			return true
   101  		}
   102  	}
   103  	return false
   104  }
   105  
   106  // UserAuthCertHook builds an authentication hook based on the security
   107  // mode and client certificate.
   108  func UserAuthCertHook(insecureMode bool, tlsState *tls.ConnectionState) (UserAuthHook, error) {
   109  	var certUsers []string
   110  
   111  	if !insecureMode {
   112  		var err error
   113  		certUsers, err = GetCertificateUsers(tlsState)
   114  		if err != nil {
   115  			return nil, err
   116  		}
   117  	}
   118  
   119  	return func(requestedUser string, clientConnection bool) (func(), error) {
   120  		// TODO(marc): we may eventually need stricter user syntax rules.
   121  		if len(requestedUser) == 0 {
   122  			return nil, errors.New("user is missing")
   123  		}
   124  
   125  		if !clientConnection && requestedUser != NodeUser {
   126  			return nil, errors.Errorf("user %s is not allowed", requestedUser)
   127  		}
   128  
   129  		// If running in insecure mode, we have nothing to verify it against.
   130  		if insecureMode {
   131  			return nil, nil
   132  		}
   133  
   134  		// The client certificate user must match the requested user,
   135  		// except if the certificate user is NodeUser, which is allowed to
   136  		// act on behalf of all other users.
   137  		if !ContainsUser(requestedUser, certUsers) && !ContainsUser(NodeUser, certUsers) {
   138  			return nil, errors.Errorf("requested user is %s, but certificate is for %s", requestedUser, certUsers)
   139  		}
   140  
   141  		return nil, nil
   142  	}, nil
   143  }
   144  
   145  // UserAuthPasswordHook builds an authentication hook based on the security
   146  // mode, password, and its potentially matching hash.
   147  func UserAuthPasswordHook(insecureMode bool, password string, hashedPassword []byte) UserAuthHook {
   148  	return func(requestedUser string, clientConnection bool) (func(), error) {
   149  		if len(requestedUser) == 0 {
   150  			return nil, errors.New("user is missing")
   151  		}
   152  
   153  		if !clientConnection {
   154  			return nil, errors.New("password authentication is only available for client connections")
   155  		}
   156  
   157  		if insecureMode {
   158  			return nil, nil
   159  		}
   160  
   161  		// If the requested user has an empty password, disallow authentication.
   162  		if len(password) == 0 || CompareHashAndPassword(hashedPassword, password) != nil {
   163  			return nil, errors.Errorf(ErrPasswordUserAuthFailed, requestedUser)
   164  		}
   165  
   166  		return nil, nil
   167  	}
   168  }
   169  
   170  // ErrPasswordUserAuthFailed is the error template for failed password auth
   171  // of a user. It should be used when the password is incorrect or the user
   172  // does not exist.
   173  const ErrPasswordUserAuthFailed = "password authentication failed for user %s"