Skip to content

Commit 8ff29e9

Browse files
committed
Validate connection security properties
1 parent e0601da commit 8ff29e9

File tree

1 file changed

+83
-4
lines changed

1 file changed

+83
-4
lines changed

sql/mysql_db/auth.go

Lines changed: 83 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"bytes"
1919
"crypto/sha1"
2020
"encoding/hex"
21+
"fmt"
2122
"net"
2223

2324
"github.com/dolthub/vitess/go/mysql"
@@ -107,7 +108,7 @@ var _ mysql.CachingStorage = (*noopCachingStorage)(nil)
107108
//
108109
// This implementation also handles authentication when a client doesn't send an auth response and
109110
// the associated user account does not have a password set.
110-
func (n noopCachingStorage) UserEntryWithCacheHash(_ *mysql.Conn, _ []byte, user string, authResponse []byte, remoteAddr net.Addr) (mysql.Getter, mysql.CacheState, error) {
111+
func (n noopCachingStorage) UserEntryWithCacheHash(conn *mysql.Conn, _ []byte, user string, authResponse []byte, remoteAddr net.Addr) (mysql.Getter, mysql.CacheState, error) {
111112
db := n.db
112113

113114
// If there is no mysql database of user info, then don't approve or reject, since we can't look at
@@ -134,6 +135,11 @@ func (n noopCachingStorage) UserEntryWithCacheHash(_ *mysql.Conn, _ []byte, user
134135
return nil, mysql.AuthRejected, mysql.NewSQLError(mysql.ERAccessDeniedError, mysql.SSAccessDeniedError, "Access denied for user '%v'", user)
135136
}
136137

138+
// validate any extra connection security requirements, such as SSL or a client cert
139+
if err = validateConnectionSecurity(userEntry, conn); err != nil {
140+
return nil, mysql.AuthRejected, err
141+
}
142+
137143
if userEntry.AuthString == "" {
138144
return sql.MysqlConnectionUser{User: user, Host: host}, mysql.AuthAccepted, nil
139145
} else {
@@ -166,7 +172,7 @@ var _ mysql.PlainTextStorage = (*sha2PlainTextStorage)(nil)
166172

167173
// UserEntryWithPassword implements the mysql.PlainTextStorage interface.
168174
// The auth framework in Vitess also passes in user certificates, but we don't support that feature yet.
169-
func (s sha2PlainTextStorage) UserEntryWithPassword(_ *mysql.Conn, user string, password string, remoteAddr net.Addr) (mysql.Getter, error) {
175+
func (s sha2PlainTextStorage) UserEntryWithPassword(conn *mysql.Conn, user string, password string, remoteAddr net.Addr) (mysql.Getter, error) {
170176
db := s.db
171177

172178
host, err := extractHostAddress(remoteAddr)
@@ -186,6 +192,11 @@ func (s sha2PlainTextStorage) UserEntryWithPassword(_ *mysql.Conn, user string,
186192
return nil, mysql.NewSQLError(mysql.ERAccessDeniedError, mysql.SSAccessDeniedError, "Access denied for user '%v'", user)
187193
}
188194

195+
// validate any extra connection security requirements, such as SSL or a client cert
196+
if err = validateConnectionSecurity(userEntry, conn); err != nil {
197+
return nil, err
198+
}
199+
189200
if len(userEntry.AuthString) > 0 {
190201
digestType, iterations, salt, _, err := mysql.DeserializeCachingSha2PasswordAuthString([]byte(userEntry.AuthString))
191202
if err != nil {
@@ -329,7 +340,7 @@ var _ mysql.HashStorage = (*nativePasswordHashStorage)(nil)
329340

330341
// UserEntryWithHash implements the mysql.HashStorage interface. This implementation is called by the MySQL
331342
// native password auth method to validate a password hash with the user's stored password hash.
332-
func (nphs *nativePasswordHashStorage) UserEntryWithHash(_ *mysql.Conn, salt []byte, user string, authResponse []byte, remoteAddr net.Addr) (mysql.Getter, error) {
343+
func (nphs *nativePasswordHashStorage) UserEntryWithHash(conn *mysql.Conn, salt []byte, user string, authResponse []byte, remoteAddr net.Addr) (mysql.Getter, error) {
333344
db := nphs.db
334345

335346
host, err := extractHostAddress(remoteAddr)
@@ -348,6 +359,12 @@ func (nphs *nativePasswordHashStorage) UserEntryWithHash(_ *mysql.Conn, salt []b
348359
if userEntry == nil || userEntry.Locked {
349360
return nil, mysql.NewSQLError(mysql.ERAccessDeniedError, mysql.SSAccessDeniedError, "Access denied for user '%v'", user)
350361
}
362+
363+
// validate any extra connection security requirements, such as SSL or a client cert
364+
if err = validateConnectionSecurity(userEntry, conn); err != nil {
365+
return nil, err
366+
}
367+
351368
if len(userEntry.AuthString) > 0 {
352369
if !validateMysqlNativePassword(authResponse, salt, userEntry.AuthString) {
353370
return nil, mysql.NewSQLError(mysql.ERAccessDeniedError, mysql.SSAccessDeniedError, "Access denied for user '%v'", user)
@@ -361,6 +378,62 @@ func (nphs *nativePasswordHashStorage) UserEntryWithHash(_ *mysql.Conn, salt []b
361378
return sql.MysqlConnectionUser{User: userEntry.User, Host: userEntry.Host}, nil
362379
}
363380

381+
// validateConnectionSecurity examines the security properties of |conn| (e.g. TLS,
382+
// selected cipher, X509 client certs) and validates specific connection properties
383+
// based on what |userEntry| has configured. An error is returned if any validation
384+
// issues were detected, otherwise nil is returned.
385+
func validateConnectionSecurity(userEntry *User, conn *mysql.Conn) error {
386+
switch userEntry.SslType {
387+
case "":
388+
// No connection security validation needed
389+
return nil
390+
case "ANY":
391+
// ANY indicates that we need any form of secure socket
392+
if !conn.TLSEnabled() {
393+
return fmt.Errorf("SSL/TLS connection required")
394+
}
395+
case "X509":
396+
// X509 means that a valid X509 client certificate is required
397+
// NOTE: cert validation (e.g. expiration date, CA chain) is handled
398+
// in the Go networking stack, so long as tls.VerifyClientCertIfGiven
399+
// is specified in the TLS configuration for the server.
400+
clientCerts := conn.GetTLSClientCerts()
401+
if len(clientCerts) == 0 {
402+
return fmt.Errorf("no client certificate provided")
403+
}
404+
// TODO: Do we need to do anything if there are multiple client certs provided?
405+
case "SPECIFIED":
406+
// Specified means that we have additional requirements on either the SSL cipher
407+
// or the X509 cert, so we need to perform additional validation checks.
408+
if userEntry.SslCipher != "" {
409+
if !conn.TLSEnabled() {
410+
return fmt.Errorf("SSL/TLS connection required")
411+
}
412+
// TODO: validate the ssl cipher
413+
// TODO: How do we get the SSL cipher in use? What are valid values you can specify in MySQL?
414+
return fmt.Errorf("SSL cipher validation not supported yet")
415+
}
416+
if userEntry.X509Issuer != "" {
417+
if len(conn.GetTLSClientCerts()) == 0 {
418+
return fmt.Errorf("no client certificate provided")
419+
}
420+
// TODO: Validate the client cert issuer
421+
return fmt.Errorf("X509 issuer validation not supported yet")
422+
}
423+
if userEntry.X509Subject != "" {
424+
if len(conn.GetTLSClientCerts()) == 0 {
425+
return fmt.Errorf("no client certificate provided")
426+
}
427+
// TODO: Validate the client cert subject
428+
return fmt.Errorf("X509 subject validation not supported yet")
429+
}
430+
default:
431+
return fmt.Errorf("unsupported ssl_type: %v", userEntry.SslType)
432+
}
433+
434+
return nil
435+
}
436+
364437
// userValidator implements the mysql.UserValidator interface. It looks up a user and host from the
365438
// associated mysql database (|db|) and validates that a user entry exists and that it is configured
366439
// for the specified authentication plugin (|authMethod|).
@@ -408,7 +481,13 @@ func (uv *userValidator) HandleUser(user string, remoteAddr net.Addr) bool {
408481
}
409482
userEntry := db.GetUser(rd, user, host, false)
410483

411-
return userEntry != nil && userEntry.Plugin == string(uv.authMethod)
484+
// If we don't find a matching user, or we find one, but it's for a different auth method,
485+
// then return false to indicate this auth method can't handle that user.
486+
if userEntry == nil || userEntry.Plugin != string(uv.authMethod) {
487+
return false
488+
}
489+
490+
return true
412491
}
413492

414493
// extractHostAddress extracts the host address from |addr|, checking to see if it is a unix socket, and if

0 commit comments

Comments
 (0)