kubernetes-vault.png
Vault 是用于处理和加密整个基础架构秘钥的中心管理服务。Vault 通过 secret 引擎管理所有的秘钥,Vault 有一套 secret 引擎可以使用。

其主要有以下功能:

  • 安全密钥存储:任意的key/value Secret都可以存储到Vault中,Vault会对这些Secret进行加密并持久化存储。后端存储支持本地磁盘、cosul等;
  • 动态密钥:Vault可以动态生成Secret,在租约到期后会自动撤销它们;
  • 数据加密:Vault可以加密和解密数据,安全团队可以自定义加密参数;
  • 租赁和续订:Vault 中的所有机密都有与其关联的租约。在租约结束时,Vault 将自动撤销该机密。客户端可以通过内置续订 API 续订租约;
  • 吊销:Vault具有对秘密吊销的内置支持。Vault 可以撤消单个机密,还可以撤销一个机密树,例如由特定用户读取的所有机密或特定类型的所有机密。在发生入侵时,吊销有助于关键滚动和锁定系统;


安装

在Linux主机上安装

在Linux主机上安装比较简单,只需要下面三步:

  1. # 安装包管理工具
  2. $ sudo yum install -y yum-utils
  3. # 添加源
  4. $ sudo yum-config-manager --add-repo https://rpm.releases.hashicorp.com/RHEL/hashicorp.repo
  5. # 安装vault
  6. $ sudo yum -y install vault


在K8S中安装

vault提供了helm包,可以使用helm进行安装。

版本说明:

  • Helm 3.0+
  • Kubernetes 1.9+
  1. # 添加repo仓库
  2. $ helm repo add hashicorp https://helm.releases.hashicorp.com
  3. # 更新本地仓库
  4. $ helm repo update
  5. # 安装vault
  6. $ helm install vault hashicorp/vault


起服务端

这里仅针对主机上安装的vault,在K8S集群中使用helm安装的vault默认已经起了服务端了。

这里已经在主机上安装了vault。

  1. $ vault version
  2. Vault v1.6.1 (6d2db3f033e02e70202bef9ec896360062b88b03)

然后以开发默认运行一个Vault服务端,正式环境不用开发模式。

  1. $ vault server -dev -dev-listen-address=0.0.0.0:8200 &
  2. ......
  3. WARNING! dev mode is enabled! In this mode, Vault runs entirely in-memory
  4. and starts unsealed with a single unseal key. The root token is already
  5. authenticated to the CLI, so you can immediately begin using Vault.
  6. You may need to set the following environment variable:
  7. $ export VAULT_ADDR='http://0.0.0.0:8200'
  8. The unseal key and root token are displayed below in case you want to
  9. seal/unseal the Vault or re-authenticate.
  10. Unseal Key: killR+cPfTR7P7HoYRt5SsMySMDv2w9WD7ljcxpXB+Q=
  11. Root Token: s.pd4FBsC1pamE21nLv3fszdI1
  12. Development mode should NOT be used in production installations

然后可以通过http://ip:8200/ui进行访问。
image.png
填入生成的Token,即可登录。
image.png

配置K8S与Vault通信

要使K8S能正常读取Vault中的Secret,则必须保证K8S和Vault能正常通信。

PS:我这里是采用Kubeadm安装的K8S集群,版本1.18.9

(1)添加环境变量,其中IP地址根据实际情况填写

  1. $ export VAULT_ADDR=http://192.168.0.153:8200

(2)开启K8S认证方式

  1. $ vault auth enable kubernetes
  2. Success! Enabled kubernetes auth method at: kubernetes/

(3)添加K8S集群配置信息

  1. $ vault write auth/kubernetes/config \
  2. kubernetes_host=https://192.168.0.153:6443 \
  3. kubernetes_ca_cert=@/etc/kubernetes/pki/ca.crt
  4. Success! Data written to: auth/kubernetes/config

(4)创建权限策略

  1. $ cat <<EOF | vault policy write vault-demo-policy -
  2. > path "sys/mounts" { capabilities = ["read"] }
  3. > path "secret/data/demo/*" { capabilities = ["read"] }
  4. > path "secret/metadata/demo/*" { capabilities = ["list"] }
  5. > EOF
  6. Success! Uploaded policy: vault-demo-policy

创建一个用于演示的demo策略。
(5)创建一个认证角色

  1. $ vault write auth/kubernetes/role/vault-demo-role \
  2. > bound_service_account_names=vault-serviceaccount \
  3. > bound_service_account_namespaces=default \
  4. > policies=vault-demo-policy \
  5. > ttl=1h
  6. Success! Data written to: auth/kubernetes/role/vault-demo-role

角色名是vault-demo-role,认证方式是RBAC认证,绑定的用户是vault-serviceaccount,策略是vault-demo-policy
(6)创建密钥

  1. $ vault kv put secret/demo/database username="coolops" password=123456
  2. Key Value
  3. --- -----
  4. created_time 2021-01-25T08:22:35.134166877Z
  5. deletion_time n/a
  6. destroyed false
  7. version 1
  8. # 查看
  9. $ vault kv get secret/demo/database
  10. ====== Metadata ======
  11. Key Value
  12. --- -----
  13. created_time 2021-01-25T08:22:35.134166877Z
  14. deletion_time n/a
  15. destroyed false
  16. version 1
  17. ====== Data ======
  18. Key Value
  19. --- -----
  20. password 123456
  21. username coolops

(7)在K8S集群中创建RBAC权限

  1. ---
  2. apiVersion: v1
  3. kind: ServiceAccount
  4. metadata:
  5. name: vault-serviceaccount
  6. ---
  7. apiVersion: rbac.authorization.k8s.io/v1beta1
  8. kind: ClusterRoleBinding
  9. metadata:
  10. name: vault-clusterrolebinding
  11. roleRef:
  12. apiGroup: rbac.authorization.k8s.io
  13. kind: ClusterRole
  14. name: system:auth-delegator
  15. subjects:
  16. - kind: ServiceAccount
  17. name: vault-serviceaccount
  18. namespace: default
  19. ---
  20. kind: Role
  21. apiVersion: rbac.authorization.k8s.io/v1
  22. metadata:
  23. name: vault-secretadmin-role
  24. rules:
  25. - apiGroups: [""]
  26. resources: ["secrets"]
  27. verbs: ["*"]
  28. ---
  29. kind: RoleBinding
  30. apiVersion: rbac.authorization.k8s.io/v1
  31. metadata:
  32. name: vault-secretadmin-rolebinding
  33. subjects:
  34. - kind: ServiceAccount
  35. name: vault-serviceaccount
  36. roleRef:
  37. kind: Role
  38. name: vault-secretadmin-role
  39. apiGroup: rbac.authorization.k8s.io

创建RBAC配置文件

  1. $ kubectl apply -f rbac.yaml
  2. serviceaccount/vault-serviceaccount created
  3. clusterrolebinding.rbac.authorization.k8s.io/vault-clusterrolebinding created
  4. role.rbac.authorization.k8s.io/vault-secretadmin-role created
  5. rolebinding.rbac.authorization.k8s.io/vault-secretadmin-rolebinding created

在K8S中使用Vault中的Secret

要获取到Vault中的Secret,有两种方式:

  • 使用vault agent在initContainer中将secret取出来
  • 使用vault SDK在程序中获取

使用initContainer方式

下面是官方的一个demo。

流程图如下:
Valut加密 - 图4
(1)创建ConfigMap

  1. apiVersion: v1
  2. data:
  3. vault-agent-config.hcl: |
  4. # Comment this out if running as sidecar instead of initContainer
  5. exit_after_auth = true
  6. pid_file = "/home/vault/pidfile"
  7. auto_auth {
  8. method "kubernetes" {
  9. mount_path = "auth/kubernetes"
  10. config = {
  11. role = "vault-demo-role"
  12. }
  13. }
  14. sink "file" {
  15. config = {
  16. path = "/home/vault/.vault-token"
  17. }
  18. }
  19. }
  20. template {
  21. destination = "/etc/secrets/index.html"
  22. contents = <<EOT
  23. <html>
  24. <body>
  25. <p>Some secrets:</p>
  26. {{- with secret "secret/demo/database" }}
  27. <ul>
  28. <li><pre>username: {{ .Data.data.username }}</pre></li>
  29. <li><pre>password: {{ .Data.data.password }}</pre></li>
  30. </ul>
  31. {{ end }}
  32. </body>
  33. </html>
  34. EOT
  35. }
  36. kind: ConfigMap
  37. metadata:
  38. name: example-vault-agent-config
  39. namespace: default

template允许将Vault里保存的Secret保存到文件。

创建pod

  1. apiVersion: v1
  2. kind: Pod
  3. metadata:
  4. name: vault-agent-example
  5. namespace: default
  6. spec:
  7. serviceAccountName: vault-serviceaccount
  8. volumes:
  9. - configMap:
  10. items:
  11. - key: vault-agent-config.hcl
  12. path: vault-agent-config.hcl
  13. name: example-vault-agent-config
  14. name: config
  15. - emptyDir: {}
  16. name: shared-data
  17. initContainers:
  18. - args:
  19. - agent
  20. - -config=/etc/vault/vault-agent-config.hcl
  21. - -log-level=debug
  22. env:
  23. - name: VAULT_ADDR
  24. value: http://192.168.0.153:8200
  25. image: registry.cn-hangzhou.aliyuncs.com/rookieops/vault:1.6.1
  26. name: vault-agent
  27. volumeMounts:
  28. - mountPath: /etc/vault
  29. name: config
  30. - mountPath: /etc/secrets
  31. name: shared-data
  32. containers:
  33. - image: nginx
  34. name: nginx-container
  35. ports:
  36. - containerPort: 80
  37. volumeMounts:
  38. - mountPath: /usr/share/nginx/html
  39. name: shared-data

注意serviceAccountName需和之前配置的保持一致

待pod运行后,可以正常获取到vault里的Secret,如下:

  1. $ kubectl get po -o wide
  2. NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
  3. nfs-client-prosioner-598d477ff6-fmgwf 1/1 Running 8 65d 172.16.7.140 ecs-968f-0005 <none> <none>
  4. traefik-5b8bb6787-dn96j 1/1 Running 0 65d 172.16.7.138 ecs-968f-0005 <none> <none>
  5. vault-agent-example 1/1 Running 0 106s 172.16.235.231 k8s-master <none> <none>
  6. $ curl 172.16.235.231
  7. <html>
  8. <body>
  9. <p>Some secrets:</p>
  10. <ul>
  11. <li><pre>username: coolops</pre></li>
  12. <li><pre>password: 123456</pre></li>
  13. </ul>
  14. </body>
  15. </html>

使用SDK方式

  1. package main
  2. import (
  3. "fmt"
  4. "io/ioutil"
  5. vaultApi "github.com/hashicorp/vault/api"
  6. )
  7. var (
  8. vaultHost string
  9. vaultCAPath string
  10. vaultServiceAccount string
  11. vaultJWTPath string
  12. )
  13. func main() {
  14. // K8S的token
  15. vaultJWTPath = "/var/run/secrets/kubernetes.io/serviceaccount/token"
  16. // sa名字
  17. vaultServiceAccount = "vault-serviceaccount"
  18. tlsConfig := &vaultApi.TLSConfig{
  19. CACert: vaultCAPath,
  20. Insecure: false,
  21. }
  22. config := vaultApi.DefaultConfig()
  23. // vault地址
  24. config.Address = fmt.Sprintf("https://%s", vaultHost)
  25. config.ConfigureTLS(tlsConfig)
  26. client, _ := vaultApi.NewClient(config)
  27. buf, _ := ioutil.ReadFile(vaultJWTPath)
  28. jwt := string(buf)
  29. options := map[string]interface{}{
  30. "jwt": jwt,
  31. "role": vaultServiceAccount,
  32. }
  33. loginSecret, _ := client.Logical().Write("auth/kubernetes/login", options)
  34. client.SetToken(loginSecret.Auth.ClientToken)
  35. // secret地址
  36. secret, _ := client.Logical().Read("database/creds/tx")
  37. fmt.Println(secret)
  38. }

最后

Vault是一个很好的工具,可以相对安全的管理一些敏感信息,不过通过上面的步骤可以看到配置相对复杂,维护成本相对较高,不过Kubernetes和Vault集成依旧是一个不错的方案。

参考: