Golang新手问题:使用database/sql库时连接失败如何从Vault获取密码
Golang新手问题:使用database/sql库时连接失败如何从Vault获取密码 我对Golang还比较陌生,如果问题描述不够清楚还请见谅。
我有一个用Go编写的常驻守护进程,它监听一个端口,并为每个新连接启动多个goroutine来处理数据。在我的脚本主函数中,有一个全局变量db,它被赋值为database/sql库的open()函数返回的连接上下文。
出于安全考虑,我们将数据库密码存储在保险库中,每隔几天就会轮换一次。我可以在首次创建连接上下文时从保险库获取密码,所有goroutine都使用相同的上下文来创建新的数据库连接。但是,当保险库轮换密码后,所有新的数据库连接都会失败。我想知道处理这种情况的最佳方法是什么,以便在失败时能从保险库获取密码并重新连接。如果是面向对象的语言,我可以扩展数据库库并重写连接函数来捕获错误,在连接失败时从保险库获取密码。在Go中是否有类似的方法?或者还有其他处理方式吗?
package main
import (
"database/sql"
_ "github.com/go-sql-driver/mysql"
"net"
)
var db *sql.DB
const port = "port number"
func main() {
db, err = sql.Open("mysql","<Connection string that contains the password fetched from vault>")
db.SetMaxOpenConns(100)
listener, err := net.Listen("tcp", ":"+port)
for {
conn, err := listener.Accept()
go handleConnection(conn)
}
}
func handleConnection(conn net.Conn) {
// Uses db variable to connect to db.
}
更多关于Golang新手问题:使用database/sql库时连接失败如何从Vault获取密码的实战教程也可以访问 https://www.itying.com/category-94-b0.html
更多关于Golang新手问题:使用database/sql库时连接失败如何从Vault获取密码的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
在Go中处理数据库密码轮换时,可以通过实现连接重试逻辑和动态更新连接字符串来解决。以下是几种可行的方案:
方案1:使用自定义连接器(Driver.Connector)
package main
import (
"database/sql"
"database/sql/driver"
"fmt"
"net"
"sync"
"time"
_ "github.com/go-sql-driver/mysql"
)
var db *sql.DB
const port = "port number"
type VaultConnector struct {
dsnTemplate string // 不包含密码的连接字符串模板
mu sync.RWMutex
currentDSN string
}
func NewVaultConnector(dsnTemplate string) *VaultConnector {
vc := &VaultConnector{
dsnTemplate: dsnTemplate,
}
vc.refreshDSN()
return vc
}
func (vc *VaultConnector) refreshDSN() {
password := fetchPasswordFromVault()
vc.mu.Lock()
vc.currentDSN = fmt.Sprintf(vc.dsnTemplate, password)
vc.mu.Unlock()
}
func (vc *VaultConnector) Connect(ctx context.Context) (driver.Conn, error) {
vc.mu.RLock()
dsn := vc.currentDSN
vc.mu.RUnlock()
// 使用标准mysql驱动创建连接
driver := mysql.MySQLDriver{}
conn, err := driver.Open(dsn)
if err != nil {
// 如果连接失败,尝试刷新密码重连
vc.refreshDSN()
vc.mu.RLock()
dsn = vc.currentDSN
vc.mu.RUnlock()
return driver.Open(dsn)
}
return conn, nil
}
func (vc *VaultConnector) Driver() driver.Driver {
return mysql.MySQLDriver{}
}
func fetchPasswordFromVault() string {
// 实现从Vault获取密码的逻辑
return "new_password_from_vault"
}
func main() {
dsnTemplate := "user:%s@tcp(localhost:3306)/dbname"
connector := NewVaultConnector(dsnTemplate)
var err error
db, err = sql.OpenDB(connector)
if err != nil {
panic(err)
}
db.SetMaxOpenConns(100)
listener, err := net.Listen("tcp", ":"+port)
if err != nil {
panic(err)
}
for {
conn, err := listener.Accept()
if err != nil {
continue
}
go handleConnection(conn)
}
}
方案2:包装sql.DB并实现重试逻辑
package main
import (
"database/sql"
"net"
"sync"
"time"
_ "github.com/go-sql-driver/mysql"
)
type VaultDB struct {
db *sql.DB
dsnTemplate string
mu sync.RWMutex
currentDSN string
}
func NewVaultDB(dsnTemplate string) *VaultDB {
vdb := &VaultDB{
dsnTemplate: dsnTemplate,
}
vdb.refreshConnection()
return vdb
}
func (vdb *VaultDB) refreshConnection() {
password := fetchPasswordFromVault()
vdb.mu.Lock()
defer vdb.mu.Unlock()
vdb.currentDSN = fmt.Sprintf(vdb.dsnTemplate, password)
if vdb.db != nil {
vdb.db.Close()
}
var err error
vdb.db, err = sql.Open("mysql", vdb.currentDSN)
if err != nil {
panic(err)
}
vdb.db.SetMaxOpenConns(100)
}
func (vdb *VaultDB) Query(query string, args ...interface{}) (*sql.Rows, error) {
vdb.mu.RLock()
db := vdb.db
vdb.mu.RUnlock()
rows, err := db.Query(query, args...)
if err != nil && isAuthenticationError(err) {
vdb.refreshConnection()
vdb.mu.RLock()
db = vdb.db
vdb.mu.RUnlock()
return db.Query(query, args...)
}
return rows, err
}
func (vdb *VaultDB) Exec(query string, args ...interface{}) (sql.Result, error) {
vdb.mu.RLock()
db := vdb.db
vdb.mu.RUnlock()
result, err := db.Exec(query, args...)
if err != nil && isAuthenticationError(err) {
vdb.refreshConnection()
vdb.mu.RLock()
db = vdb.db
vdb.mu.RUnlock()
return db.Exec(query, args...)
}
return result, err
}
func isAuthenticationError(err error) bool {
// 检查错误是否为认证错误
return err != nil && (err.Error() == "Access denied" ||
err.Error() == "Invalid authentication")
}
var vdb *VaultDB
func main() {
dsnTemplate := "user:%s@tcp(localhost:3306)/dbname"
vdb = NewVaultDB(dsnTemplate)
listener, err := net.Listen("tcp", ":"+port)
if err != nil {
panic(err)
}
for {
conn, err := listener.Accept()
if err != nil {
continue
}
go handleConnection(conn)
}
}
func handleConnection(conn net.Conn) {
defer conn.Close()
// 使用包装的数据库接口
rows, err := vdb.Query("SELECT * FROM some_table")
if err != nil {
// 处理错误
return
}
defer rows.Close()
// 处理查询结果
}
方案3:定期刷新密码
package main
import (
"database/sql"
"net"
"sync"
"time"
_ "github.com/go-sql-driver/mysql"
)
type PasswordManager struct {
currentPassword string
mu sync.RWMutex
}
func (pm *PasswordManager) GetPassword() string {
pm.mu.RLock()
defer pm.mu.RUnlock()
return pm.currentPassword
}
func (pm *PasswordManager) RefreshPassword() {
newPassword := fetchPasswordFromVault()
pm.mu.Lock()
pm.currentPassword = newPassword
pm.mu.Unlock()
}
func (pm *PasswordManager) StartRefreshLoop(interval time.Duration) {
ticker := time.NewTicker(interval)
go func() {
for range ticker.C {
pm.RefreshPassword()
}
}()
}
var passwordManager *PasswordManager
var db *sql.DB
func main() {
passwordManager = &PasswordManager{}
passwordManager.RefreshPassword()
// 每12小时刷新一次密码
passwordManager.StartRefreshLoop(12 * time.Hour)
// 创建初始数据库连接
refreshDBConnection()
listener, err := net.Listen("tcp", ":"+port)
if err != nil {
panic(err)
}
for {
conn, err := listener.Accept()
if err != nil {
continue
}
go handleConnection(conn)
}
}
func refreshDBConnection() {
password := passwordManager.GetPassword()
dsn := fmt.Sprintf("user:%s@tcp(localhost:3306)/dbname", password)
newDB, err := sql.Open("mysql", dsn)
if err != nil {
panic(err)
}
newDB.SetMaxOpenConns(100)
if db != nil {
db.Close()
}
db = newDB
}
这些方案提供了不同的处理方式:方案1通过自定义连接器在连接级别处理认证失败,方案2通过包装sql.DB在操作级别重试,方案3通过定期刷新密码预防连接失败。选择哪种方案取决于具体的应用场景和需求。

