寻找更安全的输入处理方法:Golang最佳实践

寻找更安全的输入处理方法:Golang最佳实践 我有一个查询数据库的函数,声明如下:

func pullFullObject(uname, passwd *string) MyTypeThing{

我让用户在运行时使用 flag 输入用户名和密码,并将指向用户标志的指针传递给该函数,在那里这两个字符串被传递给 sql 驱动程序。

我意识到,在程序运行期间,内存中有一个字符串包含了数据库的密码。虽然我不指望有人会获得我电脑的 root 权限,然后在内存中四处挖掘,直到找到一个指向字符串的指针,就为了访问我那愚蠢的小玩具数据库,但我无法想象一个更“重要”的系统会如何设计。

难道所有在野外运行的系统都只是让凭据在内存中闲逛吗?


更多关于寻找更安全的输入处理方法:Golang最佳实践的实战教程也可以访问 https://www.itying.com/category-94-b0.html

2 回复

内存中的凭据

为了管理身份验证和凭据,我创建了一个由认证服务器、API服务器和数据库服务器(Postgresql)组成的隔离“保险箱”。所有这些服务器只能通过内部IP地址(10.x.x.x)访问。

在认证服务器中,我将凭据同时存储在本地Postgresql数据库(永久性)和Ristretto缓存中(与具有临时时间限制的会话ID一起存储),以实现更快的访问。

唯一暴露在互联网上的是会话ID。所有凭据在这个“保险箱”内或多或少都是无法访问的。

由于所有通信都是通过保险箱内部或从保险箱发出的内部IP或本地主机完成的,因此延迟极低。因此,速度大约在零点几秒。

更多关于寻找更安全的输入处理方法:Golang最佳实践的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


在Go中处理敏感数据(如数据库密码)时,确实需要采取额外的安全措施。以下是几种更安全处理输入的方法:

1. 使用[]byte替代string

Go的string是不可变的,可能长时间驻留内存。使用[]byte允许在完成后显式清除内存:

import "unsafe"

func pullFullObject(uname, passwd []byte) MyTypeThing {
    defer func() {
        // 使用后清除密码内存
        for i := range passwd {
            passwd[i] = 0
        }
    }()
    
    // 转换为string用于数据库驱动(仅在需要时)
    username := string(uname)
    password := string(passwd)
    
    // 使用数据库连接...
    
    return result
}

2. 使用securestring或类似的安全类型

创建自定义类型来封装敏感数据:

type SecureString struct {
    value []byte
}

func NewSecureString(s string) *SecureString {
    b := []byte(s)
    return &SecureString{value: b}
}

func (ss *SecureString) Get() string {
    return string(ss.value)
}

func (ss *SecureString) Clear() {
    for i := range ss.value {
        ss.value[i] = 0
    }
}

func (ss *SecureString) String() string {
    defer ss.Clear()
    return string(ss.value)
}

// 使用示例
func pullFullObject(uname, passwd *SecureString) MyTypeThing {
    defer passwd.Clear()
    
    db, err := sql.Open("driver", 
        fmt.Sprintf("user=%s password=%s", uname.Get(), passwd.Get()))
    // ...
}

3. 使用环境变量或加密配置文件

避免在命令行参数中传递密码:

import (
    "os"
    "syscall"
)

func getPasswordFromEnv() ([]byte, error) {
    passwd := []byte(os.Getenv("DB_PASSWORD"))
    if len(passwd) == 0 {
        return nil, errors.New("password not set")
    }
    return passwd, nil
}

// 使用后清除环境变量内存
func clearEnvVar(varName string) {
    if err := syscall.Unsetenv(varName); err != nil {
        // 处理错误
    }
}

4. 使用内存锁定(mlock)防止交换到磁盘

在Linux/Unix系统上,可以锁定包含敏感数据的内存页:

import "golang.org/x/sys/unix"

func lockMemory(data []byte) error {
    return unix.Mlock(data)
}

func unlockMemory(data []byte) error {
    return unix.Munlock(data)
}

// 使用示例
func handleSensitiveData() {
    passwd := []byte("secret")
    
    if err := lockMemory(passwd); err != nil {
        // 处理错误
    }
    defer unlockMemory(passwd)
    
    // 使用密码...
    defer func() {
        for i := range passwd {
            passwd[i] = 0
        }
    }()
}

5. 使用数据库连接池与短期凭据

对于生产系统,考虑使用短期令牌或IAM角色:

import (
    "database/sql"
    "time"
)

type SecureDBConnector struct {
    getCredentials func() (user, pass string, expiry time.Time)
}

func (s *SecureDBConnector) GetConnection() (*sql.DB, error) {
    user, pass, expiry := s.getCredentials()
    
    // 使用临时凭据建立连接
    connStr := fmt.Sprintf("user=%s password=%s", user, pass)
    db, err := sql.Open("postgres", connStr)
    if err != nil {
        return nil, err
    }
    
    // 设置连接最大生命周期
    maxLifetime := time.Until(expiry) - 5*time.Minute
    if maxLifetime > 0 {
        db.SetConnMaxLifetime(maxLifetime)
    }
    
    return db, nil
}

6. 完整的示例实现

import (
    "database/sql"
    "flag"
    "fmt"
    "time"
)

type SecureInput struct {
    value      []byte
    locked     bool
}

func NewSecureInput(s string) (*SecureInput, error) {
    si := &SecureInput{value: []byte(s)}
    
    // 尝试锁定内存(可选)
    if err := si.lock(); err != nil {
        // 记录但继续执行
        fmt.Printf("Warning: could not lock memory: %v\n", err)
    }
    
    return si, nil
}

func (si *SecureInput) Get() string {
    return string(si.value)
}

func (si *SecureInput) Clear() {
    for i := range si.value {
        si.value[i] = 0
    }
    si.value = nil
    
    if si.locked {
        // 解锁内存
        si.unlock()
    }
}

func pullFullObject(uname, passwd *SecureInput) MyTypeThing {
    defer passwd.Clear()
    
    // 仅在需要时转换为string
    dbUser := uname.Get()
    dbPass := passwd.Get()
    
    connStr := fmt.Sprintf("user=%s password=%s", dbUser, dbPass)
    db, err := sql.Open("postgres", connStr)
    if err != nil {
        panic(err)
    }
    defer db.Close()
    
    // 执行查询...
    
    return MyTypeThing{}
}

func main() {
    var username, password string
    flag.StringVar(&username, "user", "", "Database username")
    flag.StringVar(&password, "pass", "", "Database password")
    flag.Parse()
    
    secureUser, _ := NewSecureInput(username)
    securePass, _ := NewSecureInput(password)
    defer securePass.Clear()
    
    result := pullFullObject(secureUser, securePass)
    _ = result
}

这些方法可以显著提高敏感数据在内存中的安全性,特别是在长期运行的生产系统中。关键是在使用后立即清除内存,并尽可能减少敏感数据以明文形式存在的时间。

回到顶部