Skip to content

Allow registering custom CredentialProviders for per-conn passwords #1042

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,4 @@ Multiplay Ltd.
Percona LLC
Pivotal Inc.
Stripe Inc.
Zendesk Inc.
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,31 @@ Examples:
* [`time_zone=%27Europe%2FParis%27`](https://dev.mysql.com/doc/refman/5.5/en/time-zone-support.html): `SET time_zone='Europe/Paris'`
* [`tx_isolation=%27REPEATABLE-READ%27`](https://dev.mysql.com/doc/refman/5.5/en/server-system-variables.html#sysvar_tx_isolation): `SET tx_isolation='REPEATABLE-READ'`

#### Non-DSN parameters

Some parameters (those that have types too complex to fit into a string) are not supported as part of a DSN string, but can only be specified by using the Connector interface. To use these parameters, set your database client up like so:

```go
dbConfig := mysql.Config {
Addr: "localhost:3306",
// ... other parameters ...
}
connector, err := mysql.NewConnector(dbConfig)
if err != nil {
panic(error)
}
db := sql.OpenDB(connector)
```

##### `CredentialProvider`

```
Type: CredentialProviderFunc
Default: nil
```

If set, this must refer to a credential provider function of type `CredentialProviderFunc`. When this is set, the `User` and `Passwd` fields in the config will be ignored; instead, each time a connection is to be opened, the credential provider function will be called to obtain a username/password to connect with. This is useful when using, for example, IAM database auth in Amazon AWS, where "passwords" are actually temporary tokens that expire.


#### Examples
```
Expand Down
27 changes: 16 additions & 11 deletions auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,11 @@ func getServerPubKey(name string) (pubKey *rsa.PublicKey) {
return
}

// CredentialProviderFunc is a function which can be used to fetch a username/password
// pair for use when opening a new MySQL connection. The first return value is the username
// and the second the password.
type CredentialProviderFunc func() (user string, password string, error error)

// Hash password using pre 4.1 (old password) method
// https://github.com/atcurtis/mariadb/blob/master/mysys/my_rnd.c
type myRnd struct {
Expand Down Expand Up @@ -237,10 +242,10 @@ func (mc *mysqlConn) sendEncryptedPassword(seed []byte, pub *rsa.PublicKey) erro
return mc.writeAuthSwitchPacket(enc)
}

func (mc *mysqlConn) auth(authData []byte, plugin string) ([]byte, error) {
func (mc *mysqlConn) auth(authData []byte, plugin string, password string) ([]byte, error) {
switch plugin {
case "caching_sha2_password":
authResp := scrambleSHA256Password(authData, mc.cfg.Passwd)
authResp := scrambleSHA256Password(authData, password)
return authResp, nil

case "mysql_old_password":
Expand All @@ -250,7 +255,7 @@ func (mc *mysqlConn) auth(authData []byte, plugin string) ([]byte, error) {
// Note: there are edge cases where this should work but doesn't;
// this is currently "wontfix":
// https://github.com/go-sql-driver/mysql/issues/184
authResp := append(scrambleOldPassword(authData[:8], mc.cfg.Passwd), 0)
authResp := append(scrambleOldPassword(authData[:8], password), 0)
return authResp, nil

case "mysql_clear_password":
Expand All @@ -259,24 +264,24 @@ func (mc *mysqlConn) auth(authData []byte, plugin string) ([]byte, error) {
}
// http://dev.mysql.com/doc/refman/5.7/en/cleartext-authentication-plugin.html
// http://dev.mysql.com/doc/refman/5.7/en/pam-authentication-plugin.html
return append([]byte(mc.cfg.Passwd), 0), nil
return append([]byte(password), 0), nil

case "mysql_native_password":
if !mc.cfg.AllowNativePasswords {
return nil, ErrNativePassword
}
// https://dev.mysql.com/doc/internals/en/secure-password-authentication.html
// Native password authentication only need and will need 20-byte challenge.
authResp := scramblePassword(authData[:20], mc.cfg.Passwd)
authResp := scramblePassword(authData[:20], password)
return authResp, nil

case "sha256_password":
if len(mc.cfg.Passwd) == 0 {
if len(password) == 0 {
return []byte{0}, nil
}
if mc.cfg.tls != nil || mc.cfg.Net == "unix" {
// write cleartext auth packet
return append([]byte(mc.cfg.Passwd), 0), nil
return append([]byte(password), 0), nil
}

pubKey := mc.cfg.pubKey
Expand All @@ -286,7 +291,7 @@ func (mc *mysqlConn) auth(authData []byte, plugin string) ([]byte, error) {
}

// encrypted password
enc, err := encryptPassword(mc.cfg.Passwd, authData, pubKey)
enc, err := encryptPassword(password, authData, pubKey)
return enc, err

default:
Expand All @@ -295,7 +300,7 @@ func (mc *mysqlConn) auth(authData []byte, plugin string) ([]byte, error) {
}
}

func (mc *mysqlConn) handleAuthResult(oldAuthData []byte, plugin string) error {
func (mc *mysqlConn) handleAuthResult(oldAuthData []byte, plugin string, password string) error {
// Read Result Packet
authData, newPlugin, err := mc.readAuthResult()
if err != nil {
Expand All @@ -315,7 +320,7 @@ func (mc *mysqlConn) handleAuthResult(oldAuthData []byte, plugin string) error {

plugin = newPlugin

authResp, err := mc.auth(authData, plugin)
authResp, err := mc.auth(authData, plugin, password)
if err != nil {
return err
}
Expand Down Expand Up @@ -352,7 +357,7 @@ func (mc *mysqlConn) handleAuthResult(oldAuthData []byte, plugin string) error {
case cachingSha2PasswordPerformFullAuthentication:
if mc.cfg.tls != nil || mc.cfg.Net == "unix" {
// write cleartext auth packet
err = mc.writeAuthSwitchPacket(append([]byte(mc.cfg.Passwd), 0))
err = mc.writeAuthSwitchPacket(append([]byte(password), 0))
if err != nil {
return err
}
Expand Down
Loading