Golang基于Vault實現敏感信息保護
背景
在應用程序的配置中,有一類信息比較敏感,比如數據庫的用戶名/密碼、云平臺的 AK/SK、各種 API keys、各類賬號/密碼等,這些信息的泄露會帶來嚴重的安全問題。
然而在實際生產活動中,這些敏感信息的管理有很大的漏洞,存在很大的泄露風險:
- 代碼或配置以明文形式記錄敏感信息,存放在代碼倉庫中,甚至誤上傳到 GitHub;
- 敏感信息的生成、分發(fā)、保管、部署全流程經多人之手,缺乏有效的管控手段;
- 敏感信息生成之后長期有效,沒有自動輪轉機制,加大了泄露風險及影響程度。
敏感信息保護是網絡安全工作的一個重要部分。
敏感信息保護
敏感信息保護是一個比較復雜的系統(tǒng)性工作,主要包括以下幾個部分:
- 要有一個專門的平臺來托管敏感信息,本文采用 HashiCorp 公司開源的 Vault 工具
- 應用程序要與該平臺集成,從平臺獲取敏感信息,并完成續(xù)租和輪轉等操作
- 部署工具要與該平臺集成,為應用程序注入登錄平臺所需的身份憑據
Vault 是一個強大的敏感信息管理工具,自帶了多種認證引擎和密碼引擎,并通過插件機制允許自定義引擎,可應用于多種常見的敏感信息保護場景,具體用法本文不做介紹,請參考Vault官方文檔。至于部署發(fā)布工具與 Vault 的集成,與所使用的部署工具及發(fā)布流水線有關,每個公司不盡相同,本文也不做詳細展開。
本文主要探討應用程序與 Vault 的集成,以數據庫憑據為例,介紹應用程序如何安全地從 Vault 獲取敏感信息,并進一步實現自動輪轉。
應用集成方案
應用程序與 Vault 的集成可以采用直接方式,即開發(fā)者自行編寫代碼實現登錄認證、Token 續(xù)租、過期再登錄以及敏感信息的獲取、續(xù)租和輪轉等邏輯,這種集成方式對應用程序有較多的代碼侵入,實現成本較高。
Vault 官方提供了一種對應用程序代碼低侵入甚至無侵入的集成方案,即 Vault Agent,它實現了與 Vault Server 的所有交互邏輯,并且還可以通過模板功能將獲取的敏感信息渲染成本地配置文件,應用程序只需要讀取該配置文件即可。
本文采用基于 Agent 的間接集成方案:Agent 負責登錄 Vault 并管理 Token 續(xù)租及過期再登錄,根據配置模板文件從 Vault 獲取所需的敏感信息,渲染成本地配置文件,管理敏感信息的續(xù)租及輪轉,并更新本地配置文件;應用程序只需讀取本地配置文件獲取敏感信息,并持續(xù)監(jiān)聽該文件,當文件變化時進行動態(tài)更新。這是一種完全解耦的間接集成方式,如下圖所示。
準備工作
1. 創(chuàng)建具有 CRUD 權限的數據庫角色
# 首先啟用數據庫密碼引擎 $ vault secrets enable database # 創(chuàng)建 MySQL 數據庫配置 $ 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 角色的憑據,并查看驗證 $ vault read database/creds/mydb-crud $ vault list sys/leases/lookup/database/creds/mydb-crud
2. 創(chuàng)建具有上述數據庫權限的 AppRole
# 啟用 Approle 認證引擎 $ vault auth enable approle # 創(chuàng)建權限策略 mydb-policy $ vault policy write mydb-policy -<<EOF #獲取憑據的權限 path "database/creds/mydb-crud" { ??capabilities = [ "read" ] } #續(xù)租憑據的權限 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/myapp
3. 獲取 AppRole 身份憑據( 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 功能實現了登錄認證、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" } } }
獲取數據庫憑據
Agent 的 Template 功能可以根據指定位置的模板文件獲取所需的敏感信息,填充、渲染成配置文件,保存在指定位置,當渲染出的結果文件發(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
通過模板語言指定敏感信息的占位符及獲取路徑,經 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
即可獲取數據庫憑據,不需要與 Vault 進行交互,實現了與 Vault 的完全解耦。
使用數據庫憑據
關于 Golang 應用程序如何讀取配置文件可以參考《淺談Golang配置管理》這篇文章。
這里僅給出使用數據庫憑據相關的代碼片段,如下:
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{}) }
數據庫憑據自動輪轉
Agent 從 Vault 獲取數據庫憑據后,會在其TTL
到期前進行續(xù)租,當因Max-TTL
限制無法續(xù)租時,會自動輪轉,重新獲取一組新的憑據,并更新在本地配置文件中。
應用程序監(jiān)聽到本地配置文件的變化時,需要讀取新的憑據,并進行動態(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
對象來更新數據庫憑據,是一種可行的方式,但是比較粗暴,會導致連接重建,在業(yè)務高峰期時可能會影響服務性能,在生產上建議尋求更優(yōu)雅、平滑的實現方式。大家有好的實現或思路可以在評論區(qū)留言分享。
總結
本文探討了基于 Vault 的敏感信息保護方案,重點介紹了應用程序通過 Agent 與 Vault 間接集成的方法,以數據庫憑據為例,具體說明了應用程序如何安全地從 Vault 獲取敏感信息,并進一步實現自動輪轉。
以上就是Golang基于Vault實現敏感信息保護的詳細內容,更多關于Go Vault敏感信息保護的資料請關注腳本之家其它相關文章!
相關文章
淺析Golang開發(fā)中goroutine的正確使用姿勢
很多初級的Gopher在學習了goroutine之后,在項目中其實使用率不高,所以這篇文章小編主要來帶大家深入了解一下goroutine的常見使用方法,希望對大家有所幫助2024-03-03