Files
lijiaoqiao/supply-api/internal/repository/db.go
Your Name aeeec34326 fix(supply-api): 修复P2-05数据库凭证日志泄露风险
1. 在DatabaseConfig中添加SafeDSN()方法,返回脱敏的连接信息
2. 在NewDB中使用SafeDSN()记录日志
3. 添加sanitizeErrorPassword()函数清理错误信息中的密码

修复的问题:P2-05 数据库凭证日志泄露风险
2026-04-03 10:06:14 +08:00

102 lines
2.5 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package repository
import (
"context"
"fmt"
"strings"
"time"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgxpool"
"lijiaoqiao/supply-api/internal/config"
)
// DB 数据库连接池
type DB struct {
Pool *pgxpool.Pool
}
// NewDB 创建数据库连接池
func NewDB(ctx context.Context, cfg config.DatabaseConfig) (*DB, error) {
dsn := cfg.DSN()
poolConfig, err := pgxpool.ParseConfig(dsn)
if err != nil {
// P2-05: 使用SafeDSN替代DSN避免在错误信息中泄露密码
return nil, fmt.Errorf("failed to parse database config for %s: %v", cfg.SafeDSN(), sanitizeErrorPassword(err, cfg.Password))
}
poolConfig.MaxConns = int32(cfg.MaxOpenConns)
poolConfig.MinConns = int32(cfg.MaxIdleConns)
poolConfig.MaxConnLifetime = cfg.ConnMaxLifetime
poolConfig.MaxConnIdleTime = cfg.ConnMaxIdleTime
poolConfig.HealthCheckPeriod = 30 * time.Second
pool, err := pgxpool.NewWithConfig(ctx, poolConfig)
if err != nil {
// P2-05: 清理错误信息中的密码
return nil, fmt.Errorf("failed to create connection pool for %s: %v", cfg.SafeDSN(), sanitizeErrorPassword(err, cfg.Password))
}
// 验证连接
if err := pool.Ping(ctx); err != nil {
pool.Close()
return nil, fmt.Errorf("failed to ping database at %s:%d: %v", cfg.Host, cfg.Port, err)
}
return &DB{Pool: pool}, nil
}
// sanitizeErrorPassword 从错误信息中清理密码
// P2-05: pgxpool.ParseConfig的错误信息可能包含完整的DSN需要清理
func sanitizeErrorPassword(err error, password string) error {
if err == nil || password == "" {
return err
}
// 将错误信息中的密码替换为***
errStr := err.Error()
safeErrStr := strings.ReplaceAll(errStr, password, "***")
if safeErrStr != errStr {
return fmt.Errorf("%s (password sanitized)", safeErrStr)
}
return err
}
// Close 关闭连接池
func (db *DB) Close() {
if db.Pool != nil {
db.Pool.Close()
}
}
// HealthCheck 健康检查
func (db *DB) HealthCheck(ctx context.Context) error {
return db.Pool.Ping(ctx)
}
// BeginTx 开始事务
func (db *DB) BeginTx(ctx context.Context) (Transaction, error) {
tx, err := db.Pool.Begin(ctx)
if err != nil {
return nil, err
}
return &txWrapper{tx: tx}, nil
}
// Transaction 事务接口
type Transaction interface {
Commit(ctx context.Context) error
Rollback(ctx context.Context) error
}
type txWrapper struct {
tx pgx.Tx
}
func (t *txWrapper) Commit(ctx context.Context) error {
return t.tx.Commit(ctx)
}
func (t *txWrapper) Rollback(ctx context.Context) error {
return t.tx.Rollback(ctx)
}