uptrace / bun

SQL-first Golang ORM
https://bun.uptrace.dev
BSD 2-Clause "Simplified" License
3.65k stars 221 forks source link

NewListener incompatible with implementations of driver.Connector for RDS IAM Auth #870

Open bmperrea opened 1 year ago

bmperrea commented 1 year ago

The standard pattern for using sql.OpenDB with a service like AWS' RDS IAM Auth that requires real-time password refresh is to implement one's own driver.Connector - an interface from the standard database/sql/driver package. There's an example implementation given here. Basically, one can create a new pgdriver.Connector every time credentials are refreshed, and implement the two methods on driver.Connector by calling the same methods on pgdriver.Connector. Here's a sketch with error handling stripped out:

type RefreshConnector struct {
    opts            []pgdriver.Option
    passwordBuilder func() string
}

func (rc *RefreshConnector) newPgDriverConnector() *pgdriver.Connector {
    password := rc.passwordBuilder()
    optsWithPassword := append(rc.opts, pgdriver.WithPassword(password))
    conn := pgdriver.NewConnector(optsWithPassword...)
}

func (rc *RefreshConnector) Connect(ctx context.Context) (driver.Conn, error) {
    connector := rc.newPgDriverConnector() // *pgdriver.Connector
    return connector.Connect(ctx)
}

func (rc *RefreshConnector) Driver() driver.Driver  {
    connector := rc.newPgDriverConnector() // *pgdriver.Connector
    return connector.Driver(ctx) // pgdriver.Driver holding a *pgdriver.Connector
}

NewListener currently does a typecast to pgdriver.Driver and then uses the pgdriver.Connector from there for the lifetime of the listener.

The trouble with limiting oneself to pgdriver.Connector is that it doesn't get the credential refresh mentioned above. With RDS IAM Auth the password expires every 15mins and cannot be extended - a new password is needed. Later on if reconnect tries to make a new connection using the db creds from the time of instantiation in NewListener it will be stuck with a password that may have expired. Consider making use of an interface instead for what's stored in NewListener to allow the client to implement credential refresh. Alternatively, support just-in-time option modification like this to solve the same problem https://github.com/jackc/pgx/issues/676.