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"