github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/pgwire/auth.go (about) 1 // Copyright 2019 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 pgwire 12 13 import ( 14 "context" 15 "crypto/tls" 16 "net" 17 18 "github.com/cockroachdb/cockroach/pkg/security" 19 "github.com/cockroachdb/cockroach/pkg/sql" 20 "github.com/cockroachdb/cockroach/pkg/sql/pgwire/hba" 21 "github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgwirebase" 22 "github.com/cockroachdb/cockroach/pkg/util/log" 23 "github.com/cockroachdb/errors" 24 ) 25 26 const ( 27 // authOK is the pgwire auth response code for successful authentication 28 // during the connection handshake. 29 authOK int32 = 0 30 // authCleartextPassword is the pgwire auth response code to request 31 // a plaintext password during the connection handshake. 32 authCleartextPassword int32 = 3 33 ) 34 35 type authOptions struct { 36 // insecure indicates that all connections for existing users must 37 // be allowed to go through. A password, if presented, must be 38 // accepted. 39 insecure bool 40 // connType is the actual type of client connection (e.g. local, 41 // hostssl, hostnossl). 42 connType hba.ConnType 43 // auth is the current HBA configuration as returned by 44 // (*Server).GetAuthenticationConfiguration(). 45 auth *hba.Conf 46 // ie is the server-wide internal executor, used to 47 // retrieve entries from system.users. 48 ie *sql.InternalExecutor 49 50 // The following fields are only used by tests. 51 52 // testingSkipAuth requires to skip authentication, not even 53 // allowing a password exchange. 54 // Note that this different from insecure auth: with no auth, no 55 // password is accepted (a protocol error is given if one is 56 // presented); with insecure auth; _any_ is accepted. 57 testingSkipAuth bool 58 // testingAuthHook, if provided, replaces the logic in 59 // handleAuthentication(). 60 testingAuthHook func(ctx context.Context) error 61 } 62 63 // handleAuthentication checks the connection's user. Errors are sent to the 64 // client and also returned. 65 // 66 // TODO(knz): handleAuthentication should discuss with the client to arrange 67 // authentication and update c.sessionArgs with the authenticated user's name, 68 // if different from the one given initially. 69 func (c *conn) handleAuthentication( 70 ctx context.Context, ac AuthConn, authOpt authOptions, execCfg *sql.ExecutorConfig, 71 ) (connClose func(), _ error) { 72 if authOpt.testingSkipAuth { 73 return nil, nil 74 } 75 if authOpt.testingAuthHook != nil { 76 return nil, authOpt.testingAuthHook(ctx) 77 } 78 79 sendError := func(err error) error { 80 _ /* err */ = writeErr(ctx, &execCfg.Settings.SV, err, &c.msgBuilder, c.conn) 81 return err 82 } 83 84 // Check that the requested user exists and retrieve the hashed 85 // password in case password authentication is needed. 86 exists, canLogin, pwRetrievalFn, validUntilFn, err := sql.GetUserHashedPassword( 87 ctx, authOpt.ie, c.sessionArgs.User, 88 ) 89 if err != nil { 90 ac.Logf(ctx, "user retrieval failed for user=%q: %v", c.sessionArgs.User, err) 91 return nil, sendError(err) 92 } 93 94 if !exists { 95 ac.Logf(ctx, "user does not exist: %q", c.sessionArgs.User) 96 return nil, sendError(errors.Errorf(security.ErrPasswordUserAuthFailed, c.sessionArgs.User)) 97 } 98 99 if !canLogin { 100 ac.Logf(ctx, "%q does not have login privilege", c.sessionArgs.User) 101 return nil, sendError(errors.Errorf( 102 "%s does not have login privilege", c.sessionArgs.User)) 103 } 104 105 // Retrieve the authentication method. 106 tlsState, hbaEntry, methodFn, err := c.findAuthenticationMethod(authOpt) 107 if err != nil { 108 ac.Logf(ctx, "auth method lookup failed: %v", err) 109 return nil, sendError(err) 110 } 111 ac.Logf(ctx, "connection matches HBA rule: %s", hbaEntry.Input) 112 113 // Ask the method to authenticate. 114 authenticationHook, err := methodFn(ctx, ac, tlsState, pwRetrievalFn, 115 validUntilFn, execCfg, hbaEntry) 116 117 if err != nil { 118 ac.Logf(ctx, "authentication pre-hook failed: %v", err) 119 return nil, sendError(err) 120 } 121 if connClose, err = authenticationHook(c.sessionArgs.User, true /* public */); err != nil { 122 ac.Logf(ctx, "authentication failed: %v", err) 123 return connClose, sendError(err) 124 } 125 126 ac.Logf(ctx, "authentication succeeded") 127 128 c.msgBuilder.initMsg(pgwirebase.ServerMsgAuth) 129 c.msgBuilder.putInt32(authOK) 130 return connClose, c.msgBuilder.finishMsg(c.conn) 131 } 132 133 func (c *conn) findAuthenticationMethod( 134 authOpt authOptions, 135 ) (tlsState tls.ConnectionState, hbaEntry *hba.Entry, methodFn AuthMethod, err error) { 136 if authOpt.insecure { 137 // Insecure connections always use "trust" no matter what, and the 138 // remaining of the configuration is ignored. 139 methodFn = authTrust 140 hbaEntry = &insecureEntry 141 return 142 } 143 144 // Look up the method from the HBA configuration. 145 var mi methodInfo 146 mi, hbaEntry, err = c.lookupAuthenticationMethodUsingRules(authOpt.connType, authOpt.auth) 147 if err != nil { 148 return 149 } 150 methodFn = mi.fn 151 152 // Check that this method can be used over this connection type. 153 if authOpt.connType&mi.validConnTypes == 0 { 154 err = errors.Newf("method %q required for this user, but unusable over this connection type", 155 hbaEntry.Method.Value) 156 return 157 } 158 159 // If the client is using SSL, retrieve the TLS state to provide as 160 // input to the method. 161 if authOpt.connType == hba.ConnHostSSL { 162 tlsConn, ok := c.conn.(*readTimeoutConn).Conn.(*tls.Conn) 163 if !ok { 164 err = errors.AssertionFailedf("server reports hostssl conn without TLS state") 165 return 166 } 167 tlsState = tlsConn.ConnectionState() 168 } 169 170 return 171 } 172 173 func (c *conn) lookupAuthenticationMethodUsingRules( 174 connType hba.ConnType, auth *hba.Conf, 175 ) (mi methodInfo, entry *hba.Entry, err error) { 176 var ip net.IP 177 if connType != hba.ConnLocal { 178 // Extract the IP address of the client. 179 tcpAddr, ok := c.conn.RemoteAddr().(*net.TCPAddr) 180 if !ok { 181 err = errors.AssertionFailedf("client address type %T unsupported", c.conn.RemoteAddr()) 182 return 183 } 184 ip = tcpAddr.IP 185 } 186 187 // Look up the method. 188 for i := range auth.Entries { 189 entry = &auth.Entries[i] 190 var connMatch bool 191 connMatch, err = entry.ConnMatches(connType, ip) 192 if err != nil { 193 // TODO(knz): Determine if an error should be reported 194 // upon unknown address formats. 195 // See: https://github.com/cockroachdb/cockroach/issues/43716 196 return 197 } 198 if !connMatch { 199 // The address does not match. 200 continue 201 } 202 if !entry.UserMatches(c.sessionArgs.User) { 203 // The user does not match. 204 continue 205 } 206 return entry.MethodFn.(methodInfo), entry, nil 207 } 208 209 // No match. 210 err = errors.Errorf("no %s entry for host %q, user %q", serverHBAConfSetting, ip, c.sessionArgs.User) 211 return 212 } 213 214 // authenticatorIO is the interface used by the connection to pass password data 215 // to the authenticator and expect an authentication decision from it. 216 type authenticatorIO interface { 217 // sendPwdData is used to push authentication data into the authenticator. 218 // This call is blocking; authenticators are supposed to consume data hastily 219 // once they've requested it. 220 sendPwdData(data []byte) error 221 // noMorePwdData is used to inform the authenticator that the client is not 222 // sending any more authentication data. This method can be called multiple 223 // times. 224 noMorePwdData() 225 // authResult blocks for an authentication decision. This call also informs 226 // the authenticator that no more auth data is coming from the client; 227 // noMorePwdData() is called internally. 228 // 229 // The auth result is either an unqualifiedIntSizer (in case the auth 230 // succeeded) or an auth error. 231 authResult() (unqualifiedIntSizer, error) 232 } 233 234 // AuthConn is the interface used by the authenticator for interacting with the 235 // pgwire connection. 236 type AuthConn interface { 237 // SendAuthRequest send a request for authentication information. After 238 // calling this, the authenticator needs to call GetPwdData() quickly, as the 239 // connection's goroutine will be blocked on providing us the requested data. 240 SendAuthRequest(authType int32, data []byte) error 241 // GetPwdData returns authentication info that was previously requested with 242 // SendAuthRequest. The call blocks until such data is available. 243 // An error is returned if the client connection dropped or if the client 244 // didn't respect the protocol. After an error has been returned, GetPwdData() 245 // cannot be called any more. 246 GetPwdData() ([]byte, error) 247 // AuthOK declares that authentication succeeded and provides a 248 // unqualifiedIntSizer, to be returned by authenticator.authResult(). Future 249 // authenticator.sendPwdData() calls fail. 250 AuthOK(unqualifiedIntSizer) 251 // AuthFail declares that authentication has failed and provides an error to 252 // be returned by authenticator.authResult(). Future 253 // authenticator.sendPwdData() calls fail. The error has already been written 254 // to the client connection. 255 AuthFail(err error) 256 // Logf logs a message on the authentication log, if auth logs 257 // are enabled. 258 Logf(ctx context.Context, format string, args ...interface{}) 259 } 260 261 // authPipe is the implementation for the authenticator and AuthConn interfaces. 262 // A single authPipe will serve as both an AuthConn and an authenticator; the 263 // two represent the two "ends" of the pipe and we'll pass data between them. 264 type authPipe struct { 265 c *conn // Only used for writing, not for reading. 266 log *log.SecondaryLogger 267 268 ch chan []byte 269 // writerDone is a channel closed by noMorePwdData(). 270 // Nil if noMorePwdData(). 271 writerDone chan struct{} 272 readerDone chan authRes 273 } 274 275 type authRes struct { 276 intSizer unqualifiedIntSizer 277 err error 278 } 279 280 func newAuthPipe(c *conn, log *log.SecondaryLogger) *authPipe { 281 ap := &authPipe{ 282 c: c, 283 log: log, 284 ch: make(chan []byte), 285 writerDone: make(chan struct{}), 286 readerDone: make(chan authRes, 1), 287 } 288 return ap 289 } 290 291 var _ authenticatorIO = &authPipe{} 292 var _ AuthConn = &authPipe{} 293 294 func (p *authPipe) sendPwdData(data []byte) error { 295 select { 296 case p.ch <- data: 297 return nil 298 case <-p.readerDone: 299 return pgwirebase.NewProtocolViolationErrorf("unexpected auth data") 300 } 301 } 302 303 func (p *authPipe) noMorePwdData() { 304 if p.writerDone == nil { 305 return 306 } 307 // A reader blocked in GetPwdData() gets unblocked with an error. 308 close(p.writerDone) 309 p.writerDone = nil 310 } 311 312 // GetPwdData is part of the AuthConn interface. 313 func (p *authPipe) GetPwdData() ([]byte, error) { 314 select { 315 case data := <-p.ch: 316 return data, nil 317 case <-p.writerDone: 318 return nil, pgwirebase.NewProtocolViolationErrorf("client didn't send required auth data") 319 } 320 } 321 322 // AuthOK is part of the AuthConn interface. 323 func (p *authPipe) AuthOK(intSizer unqualifiedIntSizer) { 324 p.readerDone <- authRes{intSizer: intSizer} 325 } 326 327 func (p *authPipe) AuthFail(err error) { 328 p.readerDone <- authRes{err: err} 329 } 330 331 func (p *authPipe) Logf(ctx context.Context, format string, args ...interface{}) { 332 if p.log == nil { 333 return 334 } 335 p.log.Logf(ctx, format, args...) 336 } 337 338 // authResult is part of the authenticator interface. 339 func (p *authPipe) authResult() (unqualifiedIntSizer, error) { 340 p.noMorePwdData() 341 res := <-p.readerDone 342 return res.intSizer, res.err 343 } 344 345 // SendAuthRequest is part of the AuthConn interface. 346 func (p *authPipe) SendAuthRequest(authType int32, data []byte) error { 347 c := p.c 348 c.msgBuilder.initMsg(pgwirebase.ServerMsgAuth) 349 c.msgBuilder.putInt32(authType) 350 c.msgBuilder.write(data) 351 return c.msgBuilder.finishMsg(c.conn) 352 }