Golang基于Vault實現(xiàn)敏感信息保護
背景
在應用程序的配置中,有一類信息比較敏感,比如數(shù)據(jù)庫的用戶名/密碼、云平臺的 AK/SK、各種 API keys、各類賬號/密碼等,這些信息的泄露會帶來嚴重的安全問題。
然而在實際生產活動中,這些敏感信息的管理有很大的漏洞,存在很大的泄露風險:
- 代碼或配置以明文形式記錄敏感信息,存放在代碼倉庫中,甚至誤上傳到 GitHub;
- 敏感信息的生成、分發(fā)、保管、部署全流程經(jīng)多人之手,缺乏有效的管控手段;
- 敏感信息生成之后長期有效,沒有自動輪轉機制,加大了泄露風險及影響程度。
敏感信息保護是網(wǎng)絡安全工作的一個重要部分。
敏感信息保護
敏感信息保護是一個比較復雜的系統(tǒng)性工作,主要包括以下幾個部分:
- 要有一個專門的平臺來托管敏感信息,本文采用 HashiCorp 公司開源的 Vault 工具
- 應用程序要與該平臺集成,從平臺獲取敏感信息,并完成續(xù)租和輪轉等操作
- 部署工具要與該平臺集成,為應用程序注入登錄平臺所需的身份憑據(jù)
Vault 是一個強大的敏感信息管理工具,自帶了多種認證引擎和密碼引擎,并通過插件機制允許自定義引擎,可應用于多種常見的敏感信息保護場景,具體用法本文不做介紹,請參考Vault官方文檔。至于部署發(fā)布工具與 Vault 的集成,與所使用的部署工具及發(fā)布流水線有關,每個公司不盡相同,本文也不做詳細展開。
本文主要探討應用程序與 Vault 的集成,以數(shù)據(jù)庫憑據(jù)為例,介紹應用程序如何安全地從 Vault 獲取敏感信息,并進一步實現(xiàn)自動輪轉。
應用集成方案
應用程序與 Vault 的集成可以采用直接方式,即開發(fā)者自行編寫代碼實現(xiàn)登錄認證、Token 續(xù)租、過期再登錄以及敏感信息的獲取、續(xù)租和輪轉等邏輯,這種集成方式對應用程序有較多的代碼侵入,實現(xiàn)成本較高。
Vault 官方提供了一種對應用程序代碼低侵入甚至無侵入的集成方案,即 Vault Agent,它實現(xiàn)了與 Vault Server 的所有交互邏輯,并且還可以通過模板功能將獲取的敏感信息渲染成本地配置文件,應用程序只需要讀取該配置文件即可。
本文采用基于 Agent 的間接集成方案:Agent 負責登錄 Vault 并管理 Token 續(xù)租及過期再登錄,根據(jù)配置模板文件從 Vault 獲取所需的敏感信息,渲染成本地配置文件,管理敏感信息的續(xù)租及輪轉,并更新本地配置文件;應用程序只需讀取本地配置文件獲取敏感信息,并持續(xù)監(jiān)聽該文件,當文件變化時進行動態(tài)更新。這是一種完全解耦的間接集成方式,如下圖所示。

準備工作
1. 創(chuàng)建具有 CRUD 權限的數(shù)據(jù)庫角色
# 首先啟用數(shù)據(jù)庫密碼引擎
$ vault secrets enable database
# 創(chuàng)建 MySQL 數(shù)據(jù)庫配置
$ export MYSQL_URL=x.x.x.x:3306
$ vault write database/config/mydb \
????plugin_name=mysql-database-plugin \
????connection_url="{{username}}:{{password}}@tcp($MYSQL_URL)/" \
????allowed_roles="mydb-crud" \
????username="root" \
????password="******"
# 說明:該用戶需要具有用戶管理權限,此處直接使用 root
# 創(chuàng)建 mydb-crud 角色(具有增刪改查完整權限)
$ vault write database/roles/mydb-crud \
????db_name=mydb \
????creation_statements="CREATE USER '{{name}}'@'%' IDENTIFIED BY '{{password}}'; GRANT SELECT, INSERT, DELETE, UPDATE ON mydb.* TO '{{name}}'@'%';" \
????default_ttl="2m" \
????max_ttl="10m"
# 說明:為方便測試,此處 TTL 設置較小,實際使用時需要評估合理的值
# 測試獲取 mydb-crud 角色的憑據(jù),并查看驗證
$ vault read database/creds/mydb-crud
$ vault list sys/leases/lookup/database/creds/mydb-crud2. 創(chuàng)建具有上述數(shù)據(jù)庫權限的 AppRole
# 啟用 Approle 認證引擎
$ vault auth enable approle
# 創(chuàng)建權限策略 mydb-policy
$ vault policy write mydb-policy -<<EOF
#獲取憑據(jù)的權限
path "database/creds/mydb-crud" {
??capabilities = [ "read" ]
}
#續(xù)租憑據(jù)的權限
path "sys/leases/+/database/creds/mydb-crud/*"?{
? capabilities = [ "update" ]
}
EOF
# 創(chuàng)建具有 mydb-policy 權限的 AppRole
$ vault write auth/approle/role/myapp token_policies="mydb-policy" \
????token_ttl=2m token_max_ttl=10m
# 查看創(chuàng)建的 AppRole
$ vault read auth/approle/role/myapp3. 獲取 AppRole 身份憑據(jù)( RoleID 和 SecretID )
# 獲取 RoleID $ vault read -field=role_id auth/approle/role/myapp/role-id >~/.roleid # 獲取 SecretID $vault write -f -field=secret_id auth/approle/role/myapp/secret-id >~/.secretid
然后由部署發(fā)布工具將 RoleID 和 SecretID 注入到應用程序所在服務器的約定位置文件中。
登錄認證
Agent 的 Auto_Auth 功能實現(xiàn)了登錄認證、Token 續(xù)租和過期再登錄等邏輯,允許指定認證方法和 Token 保存位置。這里采用 AppRole 認證,需要指定 RoleID 和 SecretID 兩個文件的位置(由部署發(fā)布工具注入)。
auto-auth 配置塊如下所示:
auto_auth {
method {
type = "approle"
config = {
role_id_file_path = "/vault/config/approle/roleid"
secret_id_file_path = "/vault/config/approle/secretid"
remove_secret_id_file_after_reading = false
}
}
sink "file" {
config = {
path = "/tmp/.vault-token-via-agent"
}
}
}獲取數(shù)據(jù)庫憑據(jù)
Agent 的 Template 功能可以根據(jù)指定位置的模板文件獲取所需的敏感信息,填充、渲染成配置文件,保存在指定位置,當渲染出的結果文件發(fā)生變化時還可以執(zhí)行給定的命令。
template 相關的配置塊如下所示:
template_config {
exit_on_retry_failure = true
}
template {
error_on_missing_key = true
source = "/vault/config/appconf/config.ctmpl"
destination = "/vault/config/appconf/config.yaml.tmp"
exec {
command = ["dd", "if=/vault/config/appconf/config.yaml.tmp", "of=/vault/config/appconf/config.yaml" ]
timeout = "5s"
}
}配置模板文件config.ctmpl通過模板語言指定敏感信息的占位符及獲取路徑,經(jīng) Agent 渲染后生成應用程序能識別的配置文件config.yaml。配置模板文件的相關片段如下所示:
# config.ctmpl
database:
mysql:
{{- with secret "database/creds/mydb-crud" }}
username: {{ .Data.username }}
password: {{ .Data.password }}
{{- end }}
address : x.x.x.x:3306
dbname : mydb
options : charset=utf8mb4&parseTime=True&loc=Local應用程序讀取本地配置文件config.yaml即可獲取數(shù)據(jù)庫憑據(jù),不需要與 Vault 進行交互,實現(xiàn)了與 Vault 的完全解耦。
使用數(shù)據(jù)庫憑據(jù)
關于 Golang 應用程序如何讀取配置文件可以參考《淺談Golang配置管理》這篇文章。
這里僅給出使用數(shù)據(jù)庫憑據(jù)相關的代碼片段,如下:
var (
mysqlUsername string
mysqlPassword string
mysqlAddress string
mysqlDBname string
mysqlOptions string
)
func initConfig() {
mysqlUsername = Config.Database.MySQL.Username
mysqlPassword = Config.Database.MySQL.Password
mysqlAddress = Config.Database.MySQL.Address
mysqlDBname = Config.Database.MySQL.DBname
mysqlOptions = Config.Database.MySQL.Options
}
func connectMySQL() (*gorm.DB, error) {
msyqlDSN := fmt.Sprintf("%s:%s@tcp(%s)/%s?%s", mysqlUsername,
mysqlPassword, mysqlAddress, mysqlDBname, mysqlOptions)
return gorm.Open(mysql.Open(msyqlDSN), &gorm.Config{})
}數(shù)據(jù)庫憑據(jù)自動輪轉
Agent 從 Vault 獲取數(shù)據(jù)庫憑據(jù)后,會在其TTL到期前進行續(xù)租,當因Max-TTL限制無法續(xù)租時,會自動輪轉,重新獲取一組新的憑據(jù),并更新在本地配置文件中。
應用程序監(jiān)聽到本地配置文件的變化時,需要讀取新的憑據(jù),并進行動態(tài)加載。配置動態(tài)更新的具體方法可以參考《淺談Golang配置管理》這篇文章。
這里僅給出配置動態(tài)加載相關的示例代碼,如下:
var db *gorm.DB
var dbLocker sync.Mutex
func reconnectMySQL() {
// get new mysql creds
creds := getNewMySQLCreds()
if creds.Username == mysqlUsername && creds.Password == mysqlPassword {
log.Println("MySQL creds not changed, skip mysql reconnection.")
return
}
dbLocker.Lock()
defer dbLocker.Unlock()
// re-connect mysql with new creds
mysqlUsername = creds.Username
mysqlPassword = creds.Password
d, err := connectMySQL()
if err != nil {
log.Println("MySQL connect failed:", err)
return
}
// setupDatabase(d)
db = d
}說明:上述示例代碼通過重建gorm.DB對象來更新數(shù)據(jù)庫憑據(jù),是一種可行的方式,但是比較粗暴,會導致連接重建,在業(yè)務高峰期時可能會影響服務性能,在生產上建議尋求更優(yōu)雅、平滑的實現(xiàn)方式。大家有好的實現(xiàn)或思路可以在評論區(qū)留言分享。
總結
本文探討了基于 Vault 的敏感信息保護方案,重點介紹了應用程序通過 Agent 與 Vault 間接集成的方法,以數(shù)據(jù)庫憑據(jù)為例,具體說明了應用程序如何安全地從 Vault 獲取敏感信息,并進一步實現(xiàn)自動輪轉。
以上就是Golang基于Vault實現(xiàn)敏感信息保護的詳細內容,更多關于Go Vault敏感信息保護的資料請關注腳本之家其它相關文章!
相關文章
Go語言高效I/O并發(fā)處理雙緩沖和Exchanger模式實例探索
這篇文章主要介紹了Go語言高效I/O并發(fā)處理雙緩沖和Exchanger模式實例探索,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2024-01-01

