如何为运行中的HashiCorp Vault服务更新TLS证书
通过docker-compose部署HashiCorp Vault、Consul及自定义Golang开发的VaultManager三个独立容器,Vault使用自签名TLS证书。VaultManager启动时会生成新的.crt和.key证书并覆盖Vault的证书路径,需要实现Vault自动加载新证书的功能,无需人工干预。此前尝试在VaultManager容器内执行docker restart Vault提示docker command not found,同时希望了解如何使用SIGHUP信号实现证书重载。
附原配置:
docker-compose.yaml
version: '3.6' services: vault: image: imagename networks: - nwname command: server -config=/vault/config/vault-config.json cap_add: - IPC_LOCK restart: always consul: image: imagename networks: - nwname command: agent -server -bind 0.0.0.0 -client 0.0.0.0 -bootstrap-expect 1 -config-file=/consul/config/config.json restart: always vaultmanager: image: imagename devices: - "/dev/tpm0:/dev/tpm0" networks: - nwname restart: always networks: nwname: name: nwname driver: bridge
vault-config.json
{ "backend": { "consul": { "address": "consul:8500", "path": "vault/" } }, "listener": { "tcp":{ "address": "0.0.0.0:8200", "tls_disable": 0, "tls_cert_file" : "/vault/config/certificate.crt", "tls_key_file" : "/vault/config/private.key" } }, "ui": true }
核心前提:确保证书文件可被Vault容器访问
首先需要让VaultManager生成的新证书能被Vault容器读取,通过共享卷实现:
修改
docker-compose.yaml,添加共享证书卷:version: '3.6' services: vault: image: imagename networks: - nwname command: server -config=/vault/config/vault-config.json cap_add: - IPC_LOCK restart: always volumes: - vault-certs:/vault/config/certs # 挂载共享卷到Vault的证书目录 container_name: vault # 指定容器名,方便后续操作 consul: image: imagename networks: - nwname command: agent -server -bind 0.0.0.0 -client 0.0.0.0 -bootstrap-expect 1 -config-file=/consul/config/config.json restart: always vaultmanager: image: imagename devices: - "/dev/tpm0:/dev/tpm0" networks: - nwname restart: always volumes: - vault-certs:/app/certs # 挂载共享卷到VaultManager的证书生成目录 networks: nwname: name: nwname driver: bridge volumes: vault-certs: # 定义named volume修改
vault-config.json的证书路径为共享卷路径:{ "backend": { "consul": { "address": "consul:8500", "path": "vault/" } }, "listener": { "tcp":{ "address": "0.0.0.0:8200", "tls_disable": 0, "tls_cert_file" : "/vault/config/certs/certificate.crt", "tls_key_file" : "/vault/config/certs/private.key" } }, "ui": true }调整VaultManager的证书生成逻辑,将新证书输出到共享卷路径
/app/certs(对应上面的挂载配置),并设置文件权限为644,确保Vault容器的vault用户可读取。
方案1:通过SIGHUP信号触发Vault证书重载
Vault支持接收SIGHUP信号重新加载TLS证书,无需重启服务,实现优雅重载。需要让VaultManager能给Vault容器发送该信号,有两种实现方式:
方式A:挂载Docker Socket到VaultManager容器
让VaultManager容器直接调用Docker命令发送信号:
修改
docker-compose.yaml的vaultmanager服务,添加Docker相关挂载:vaultmanager: image: imagename devices: - "/dev/tpm0:/dev/tpm0" networks: - nwname restart: always volumes: - vault-certs:/app/certs - /var/run/docker.sock:/var/run/docker.sock # 挂载Docker socket - /usr/bin/docker:/usr/bin/docker # 挂载Docker二进制文件(部分环境需要)在VaultManager的Golang代码中执行以下命令发送SIGHUP:
cmd := exec.Command("docker", "kill", "-s", "HUP", "vault") err := cmd.Run() if err != nil { // 处理错误 }
注意:挂载Docker Socket会赋予VaultManager容器主机的Root权限,仅在可信环境中使用。
方式B:通过共享PID命名空间(不推荐生产环境)
将Vault容器的PID命名空间共享到主机,VaultManager可通过进程ID发送信号:
修改
docker-compose.yaml的vault服务:vault: image: imagename networks: - nwname command: server -config=/vault/config/vault-config.json cap_add: - IPC_LOCK restart: always volumes: - vault-certs:/vault/config/certs pid: "host" # 共享主机PID命名空间在VaultManager中通过
pgrep vault找到进程ID,再发送SIGHUP:// 先获取Vault进程ID pgrepCmd := exec.Command("pgrep", "vault") pidOutput, err := pgrepCmd.Output() if err != nil { // 处理错误 } pid := strings.TrimSpace(string(pidOutput)) // 发送SIGHUP信号 killCmd := exec.Command("kill", "-s", "HUP", pid) err = killCmd.Run() if err != nil { // 处理错误 }
注意:共享PID命名空间会破坏容器隔离性,存在安全风险,不建议生产环境使用。
方案2:通过Vault API触发证书重载(推荐)
Vault提供了官方API端点/v1/sys/reload用于重载配置和证书,无需依赖Docker环境,更安全符合微服务隔离原则:
确保VaultManager持有具有
sys/reload权限的Vault Token(可通过Root Token生成受限Token)。在VaultManager的Golang代码中调用API:
import ( "fmt" "net/http" "strings" "crypto/tls" ) func reloadVaultCert() error { client := &http.Client{ // 若Vault使用自签名证书,需跳过证书验证(生产环境建议导入CA证书) Transport: &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, }, } req, err := http.NewRequest("POST", "https://vault:8200/v1/sys/reload", nil) if err != nil { return err } req.Header.Set("X-Vault-Token", "your-vault-token") resp, err := client.Do(req) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return fmt.Errorf("reload failed with status: %s", resp.Status) } return nil }
注意:生产环境中不要跳过证书验证,应将Vault的CA证书导入VaultManager的信任列表。
内容的提问来源于stack exchange,提问作者V. Periyasamy

