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  }