github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/pgwire/auth_methods.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  	"bytes"
    15  	"context"
    16  	"crypto/tls"
    17  	"fmt"
    18  
    19  	"github.com/cockroachdb/cockroach/pkg/clusterversion"
    20  	"github.com/cockroachdb/cockroach/pkg/security"
    21  	"github.com/cockroachdb/cockroach/pkg/sql"
    22  	"github.com/cockroachdb/cockroach/pkg/sql/pgwire/hba"
    23  	"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
    24  	"github.com/cockroachdb/cockroach/pkg/util/timeutil"
    25  	"github.com/cockroachdb/errors"
    26  )
    27  
    28  // This file contains the methods that are accepted to perform
    29  // authentication of users during the pgwire connection handshake.
    30  //
    31  // Which method are accepted for which user is selected using
    32  // the HBA config loaded into the cluster setting
    33  // server.host_based_authentication.configuration.
    34  //
    35  // Other methods can be added using RegisterAuthMethod(). This is done
    36  // e.g. in the CCL modules to add support for GSS authentication using
    37  // Kerberos.
    38  
    39  func loadDefaultMethods() {
    40  	// The "password" method requires a clear text password.
    41  	//
    42  	// Care should be taken by administrators to only accept this auth
    43  	// method over secure connections, e.g. those encrypted using SSL.
    44  	RegisterAuthMethod("password", authPassword, clusterversion.Version19_1, hba.ConnAny, nil)
    45  
    46  	// The "cert" method requires a valid client certificate for the
    47  	// user attempting to connect.
    48  	//
    49  	// This method is only usable over SSL connections.
    50  	RegisterAuthMethod("cert", authCert, clusterversion.Version19_1, hba.ConnHostSSL, nil)
    51  
    52  	// The "cert-password" method requires either a valid client
    53  	// certificate for the connecting user, or, if no cert is provided,
    54  	// a cleartext password.
    55  	RegisterAuthMethod("cert-password", authCertPassword, clusterversion.Version19_1, hba.ConnAny, nil)
    56  
    57  	// The "reject" method rejects any connection attempt that matches
    58  	// the current rule.
    59  	RegisterAuthMethod("reject", authReject, clusterversion.VersionAuthLocalAndTrustRejectMethods, hba.ConnAny, nil)
    60  
    61  	// The "trust" method accepts any connection attempt that matches
    62  	// the current rule.
    63  	RegisterAuthMethod("trust", authTrust, clusterversion.VersionAuthLocalAndTrustRejectMethods, hba.ConnAny, nil)
    64  
    65  }
    66  
    67  // AuthMethod defines a method for authentication of a connection.
    68  type AuthMethod func(
    69  	ctx context.Context,
    70  	c AuthConn,
    71  	tlsState tls.ConnectionState,
    72  	pwRetrieveFn PasswordRetrievalFn,
    73  	pwValidUntilFn PasswordValidUntilFn,
    74  	execCfg *sql.ExecutorConfig,
    75  	entry *hba.Entry,
    76  ) (security.UserAuthHook, error)
    77  
    78  // PasswordRetrievalFn defines a method to retrieve the hashed
    79  // password for the user logging in.
    80  type PasswordRetrievalFn = func(context.Context) ([]byte, error)
    81  
    82  // PasswordValidUntilFn defines a method to retrieve the expiration time
    83  // of the user's password.
    84  type PasswordValidUntilFn = func(context.Context) (*tree.DTimestamp, error)
    85  
    86  func authPassword(
    87  	ctx context.Context,
    88  	c AuthConn,
    89  	_ tls.ConnectionState,
    90  	pwRetrieveFn PasswordRetrievalFn,
    91  	pwValidUntilFn PasswordValidUntilFn,
    92  	_ *sql.ExecutorConfig,
    93  	_ *hba.Entry,
    94  ) (security.UserAuthHook, error) {
    95  	if err := c.SendAuthRequest(authCleartextPassword, nil /* data */); err != nil {
    96  		return nil, err
    97  	}
    98  	pwdData, err := c.GetPwdData()
    99  	if err != nil {
   100  		return nil, err
   101  	}
   102  	password, err := passwordString(pwdData)
   103  	if err != nil {
   104  		return nil, err
   105  	}
   106  	hashedPassword, err := pwRetrieveFn(ctx)
   107  	if err != nil {
   108  		return nil, err
   109  	}
   110  	if len(hashedPassword) == 0 {
   111  		c.Logf(ctx, "user has no password defined")
   112  	}
   113  
   114  	validUntil, err := pwValidUntilFn(ctx)
   115  	if err != nil {
   116  		return nil, err
   117  	}
   118  	if validUntil != nil {
   119  		if validUntil.Sub(timeutil.Now()) < 0 {
   120  			c.Logf(ctx, "password is expired")
   121  			return nil, errors.New("password is expired")
   122  		}
   123  	}
   124  
   125  	return security.UserAuthPasswordHook(
   126  		false /*insecure*/, password, hashedPassword,
   127  	), nil
   128  }
   129  
   130  func passwordString(pwdData []byte) (string, error) {
   131  	// Make a string out of the byte array.
   132  	if bytes.IndexByte(pwdData, 0) != len(pwdData)-1 {
   133  		return "", fmt.Errorf("expected 0-terminated byte array")
   134  	}
   135  	return string(pwdData[:len(pwdData)-1]), nil
   136  }
   137  
   138  func authCert(
   139  	_ context.Context,
   140  	_ AuthConn,
   141  	tlsState tls.ConnectionState,
   142  	_ PasswordRetrievalFn,
   143  	_ PasswordValidUntilFn,
   144  	_ *sql.ExecutorConfig,
   145  	_ *hba.Entry,
   146  ) (security.UserAuthHook, error) {
   147  	if len(tlsState.PeerCertificates) == 0 {
   148  		return nil, errors.New("no TLS peer certificates, but required for auth")
   149  	}
   150  	// Normalize the username contained in the certificate.
   151  	tlsState.PeerCertificates[0].Subject.CommonName = tree.Name(
   152  		tlsState.PeerCertificates[0].Subject.CommonName,
   153  	).Normalize()
   154  	return security.UserAuthCertHook(false /*insecure*/, &tlsState)
   155  }
   156  
   157  func authCertPassword(
   158  	ctx context.Context,
   159  	c AuthConn,
   160  	tlsState tls.ConnectionState,
   161  	pwRetrieveFn PasswordRetrievalFn,
   162  	pwValidUntilFn PasswordValidUntilFn,
   163  	execCfg *sql.ExecutorConfig,
   164  	entry *hba.Entry,
   165  ) (security.UserAuthHook, error) {
   166  	var fn AuthMethod
   167  	if len(tlsState.PeerCertificates) == 0 {
   168  		c.Logf(ctx, "no client certificate, proceeding with password authentication")
   169  		fn = authPassword
   170  	} else {
   171  		c.Logf(ctx, "client presented certificate, proceeding with certificate validation")
   172  		fn = authCert
   173  	}
   174  	return fn(ctx, c, tlsState, pwRetrieveFn, pwValidUntilFn, execCfg, entry)
   175  }
   176  
   177  func authTrust(
   178  	_ context.Context,
   179  	_ AuthConn,
   180  	_ tls.ConnectionState,
   181  	_ PasswordRetrievalFn,
   182  	_ PasswordValidUntilFn,
   183  	_ *sql.ExecutorConfig,
   184  	_ *hba.Entry,
   185  ) (security.UserAuthHook, error) {
   186  	return func(_ string, _ bool) (func(), error) { return nil, nil }, nil
   187  }
   188  
   189  func authReject(
   190  	_ context.Context,
   191  	_ AuthConn,
   192  	_ tls.ConnectionState,
   193  	_ PasswordRetrievalFn,
   194  	_ PasswordValidUntilFn,
   195  	_ *sql.ExecutorConfig,
   196  	_ *hba.Entry,
   197  ) (security.UserAuthHook, error) {
   198  	return func(_ string, _ bool) (func(), error) {
   199  		return nil, errors.New("authentication rejected by configuration")
   200  	}, nil
   201  }