Golang中存储Postgres凭据的最佳实践
Golang中存储Postgres凭据的最佳实践 你好
我正在为一个服务编写一个命令行工具,我们需要直接与Postgres数据库通信。有什么方法可以将登录凭据存储到(客户端的)磁盘上吗?有没有一些第三方包之类的?
// Micke
3 回复
通常使用环境变量,例如:
DATABASE_URL=postgres://...
更多信息:
更多关于Golang中存储Postgres凭据的最佳实践的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
要么每次向命令行界面的用户询问凭据,要么确保其权限设置为600(或在访问控制列表下设置更严格的权限)。
在这里进行加密是相当没有意义的,因为任何加密要么需要在客户端上提供解密密钥,要么需要实现与服务器的交换机制,而针对该服务器又需要进行身份验证,这又需要公开可用的密钥或在每次访问时由用户提供……
或者,可以使用 HashiCorps Vault。(这仍然是一个基于服务器的解决方案,需要一些本地密钥,尽管在紧急情况下这些密钥很容易撤销。)
在Golang中存储Postgres凭据,推荐使用以下两种主要方式:
1. 使用操作系统密钥环(推荐)
macOS - 使用keychain
import (
"github.com/zalando/go-keyring"
)
func storeCredentials() {
service := "myapp"
user := "db_user"
password := "secret_password"
err := keyring.Set(service, user, password)
if err != nil {
// 处理错误
}
}
func getCredentials() (string, error) {
service := "myapp"
user := "db_user"
password, err := keyring.Get(service, user)
if err != nil {
return "", err
}
return password, nil
}
Windows - 使用Credential Manager
import (
"github.com/danieljoos/wincred"
)
func storeWindowsCreds() {
cred := wincred.NewGenericCredential("myapp_postgres")
cred.CredentialBlob = []byte("secret_password")
cred.UserName = "db_user"
cred.Persist = wincred.PersistLocalMachine
err := cred.Write()
if err != nil {
// 处理错误
}
}
2. 加密本地配置文件
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/base64"
"encoding/json"
"io"
"os"
"path/filepath"
)
type Config struct {
Host string `json:"host"`
Port int `json:"port"`
Username string `json:"username"`
Password string `json:"password"`
Database string `json:"database"`
}
func encryptConfig(key []byte, config Config) error {
configPath := filepath.Join(os.Getenv("HOME"), ".myapp", "config.enc")
// 序列化配置
data, err := json.Marshal(config)
if err != nil {
return err
}
// 创建AES cipher
block, err := aes.NewCipher(key)
if err != nil {
return err
}
// 创建GCM
gcm, err := cipher.NewGCM(block)
if err != nil {
return err
}
// 生成nonce
nonce := make([]byte, gcm.NonceSize())
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
return err
}
// 加密数据
encrypted := gcm.Seal(nonce, nonce, data, nil)
// Base64编码存储
encoded := base64.StdEncoding.EncodeToString(encrypted)
// 确保目录存在
os.MkdirAll(filepath.Dir(configPath), 0700)
// 写入文件
return os.WriteFile(configPath, []byte(encoded), 0600)
}
func decryptConfig(key []byte) (Config, error) {
configPath := filepath.Join(os.Getenv("HOME"), ".myapp", "config.enc")
var config Config
// 读取文件
encoded, err := os.ReadFile(configPath)
if err != nil {
return config, err
}
// Base64解码
encrypted, err := base64.StdEncoding.DecodeString(string(encoded))
if err != nil {
return config, err
}
// 创建AES cipher
block, err := aes.NewCipher(key)
if err != nil {
return config, err
}
// 创建GCM
gcm, err := cipher.NewGCM(block)
if err != nil {
return config, err
}
// 提取nonce
nonceSize := gcm.NonceSize()
nonce, ciphertext := encrypted[:nonceSize], encrypted[nonceSize:]
// 解密数据
data, err := gcm.Open(nil, nonce, ciphertext, nil)
if err != nil {
return config, err
}
// 反序列化配置
err = json.Unmarshal(data, &config)
return config, err
}
3. 使用pgpass文件(PostgreSQL标准)
import (
"bufio"
"fmt"
"os"
"path/filepath"
"strings"
)
func readPgpassFile(host string, port int, database string, username string) (string, error) {
pgpassPath := filepath.Join(os.Getenv("HOME"), ".pgpass")
file, err := os.Open(pgpassPath)
if err != nil {
return "", err
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
if strings.HasPrefix(line, "#") || strings.TrimSpace(line) == "" {
continue
}
parts := strings.Split(line, ":")
if len(parts) != 5 {
continue
}
lineHost := parts[0]
linePort := parts[1]
lineDatabase := parts[2]
lineUsername := parts[3]
password := parts[4]
// 支持通配符匹配
if (lineHost == "*" || lineHost == host) &&
(linePort == "*" || linePort == fmt.Sprintf("%d", port)) &&
(lineDatabase == "*" || lineDatabase == database) &&
(lineUsername == "*" || lineUsername == username) {
return password, nil
}
}
return "", fmt.Errorf("no matching password found in pgpass file")
}
4. 集成示例
import (
"database/sql"
"fmt"
_ "github.com/lib/pq"
"github.com/zalando/go-keyring"
)
func getDBConnection() (*sql.DB, error) {
// 从密钥环获取密码
password, err := keyring.Get("myapp_postgres", "production")
if err != nil {
return nil, fmt.Errorf("failed to get credentials: %v", err)
}
connStr := fmt.Sprintf(
"host=%s port=%d user=%s password=%s dbname=%s sslmode=disable",
"localhost", 5432, "postgres", password, "mydb",
)
db, err := sql.Open("postgres", connStr)
if err != nil {
return nil, err
}
return db, nil
}
这些方法提供了不同级别的安全性和便利性。操作系统密钥环提供了最高级别的安全性,而加密配置文件则提供了更多的灵活性。pgpass文件是PostgreSQL生态系统的标准方式,与其他PostgreSQL工具兼容。

