从业务角度使用Vault加密configmap数据

目前,我们的 ConfigMap 中存储了大量的 token 数据。每个服务挂载一个 ConfigMap,而 ConfigMap 中包含多个配置文件 (conf),每个配置文件中又包含多个 token,有的甚至包含几十个 token。

在此之前,我测试了从 Deployment 资源获取 Vault 的 secret 的方式,发现这种方法存在明显的弊端:

  1. 服务重启问题:增删操作会导致服务重启。
  2. 冗余的 Vault 容器:每个服务Sidecar 一个 Vault 容器,看起来非常冗余。
  3. 读取失败影响服务:在测试过程中,如果存在 secret 读取失败(例如权限错误或 key 不存在),init 容器会挂起,从而影响整个服务。

为了将 Vault 投入线上环境,需要使用动态配置,即从代码中使用 JWT 去请求 secret。这个步骤我已基本实现,如果要上线的话还需要很多地方需要完善,secret的增删改查、维护、vault policy的修改、vault role的修改、平滑从configmap迁移到vault,这里面需要大量的人为工作,手工或自动化。这不包括vault与开发代码的兼容性工作,我们需要和开发去沟通vault的secret存储设计(策略 权限)和名称等细节。

一、单点vault部署

1.1、创建vault所需pvc(可选)

部署vault

cat >vault-pvc.yaml<<EOF
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: vault-storage
  namespace: vault
spec:
  accessModes:
    - ReadWriteMany
  storageClassName: dev-eks-efs
  resources:
    requests:
      storage: 5Gi
EOF

1.2、创建vault helm vaules文件

cat >custom-vaules.yaml<<EOF
server:
  dataStorage:
    enabled: true
    size: 5Gi
    storageClass: dev-eks-efs
    accessMode: ReadWriteMany
    existingClaim: vault-storage
  standalone:
    enabled: true  # 根据你的需求启用单实例模式或禁用以使用 HA
ui:
  enabled: true  # 启用 Vault UI
EOF

1.3、安装vault

helm repo add hashicorp https://helm.releases.hashicorp.com
helm upgrade --install vault hashicorp/vault     --namespace vault --values custom-values.yaml 

1.4、查看容器日志

kubectl logs -f -n vault vault-0
这里会看到容器是等待初始化的一个状态

1.5、初始化vault

vault命令都是进入容器以后执行的 kubectl exec -it vault-0 -n vault sh

vault operator init

我们需要将5个key中的3个key解禁,保存好这里的Root Token!

Unseal Key 1: xx
Unseal Key 2:xx
Unseal Key 3: xx
Unseal Key 4: xx
Unseal Key 5: xx
Initial Root Token: xx

1.6、解禁key

vault operator unseal <Unseal_Key_1>
vault operator unseal <Unseal_Key_2>
vault operator unseal <Unseal_Key_3>

1.7、使用root token登录vault

这里更换成自己的token,上面的初始化提示中会输出

vault login $root_token

1.8、测试kv2功能

登录成功后这一步肯定成功

vault secrets enable -path=internal kv-v2

准备工作已经完成了

二、配置vault secret

2.1、启用一个服务用的path

我使用k8s的namesapce来命名主path

vault secrets enable -path=chat-test kv-v2

创建完成后查看一下

vault secrets list

2.2、批量创建一些vault secret

容器的解释器是sh,需要替换你自己的root_token,另外容器要编辑文件需要去/tmp目录
最后的路径表示服务名称,需要注意的kv2会自动在主路径后面添加一个data路径,API查询的路径是chat-test/data/backend/chat-ai,授权和代码也同样。

# 创建key

#!/bin/sh

# 定义 Vault 存储路径
vault_path="chat-test/backend/chat-ai"

# 登录 Vault(确保已解封并可以访问)
vault login $root_token  # 将这里替换为你的实际 Vault Token
if [ $? -ne 0 ]; then
  echo "登录失败,请检查 Token 或 Vault 服务器状态。"
  exit 1
fi

echo "登录成功"

# 定义要存储的 token 列表
tokens="gemini-pro_1:AIzaSyAX
gemini-pro_2:AzayAXx43h6wk
gemini-pro_3:AzaSyAtfb1giUt
gemini-pro_4:AIaSy9oro3Z5K
gemini-pro_5:AzaSyDm_dPznU
han10:89443b5EbF4F5773dC
han4:5E6A7aC79336F183"

# 将 token 存储到 Vault
echo "$tokens" | while IFS=: read -r key token; do
  vault kv put "$vault_path/$key" token="$token"
  if [ $? -ne 0 ]; then
    echo "Failed to store token for $key in Vault."
  else
    echo "Stored token for $key in Vault."
  fi
done

创建完成后可以list一下

vault kv list chat-test/backend/chat-ai

在这里插入图片描述

2.3、创建vault服务策略

kv2会自动将数据路径主path后面添加一个data,将元数据路径的主path后面添加一个metadata,代码也一样。只是我们vault客户端命令在查询时不需要手工添加data和metadata,还是按照我们创建的chat-test/backend/chat-ai/ 操作即可

cat >chat-ai.hcl<<EOF
# 对数据路径的权限
path "chat-test/data/backend/chat-ai/*" {
  capabilities = ["create", "read", "update", "delete", "list"]
}

# 对元数据路径的权限
path "chat-test/metadata/backend/chat-ai/*" {
  capabilities = ["create", "read", "update", "delete", "list"]
}
EOF
# 创建chat-api-policy策略
vault policy write chat-ai-policy chat-ai.hcl

2.4、vault启用k8s auth

vault auth enable kubernetes

2.5、vault启用对k8s的token验证

# 变量都是容器固定位置
vault write auth/kubernetes/config \
        token_reviewer_jwt="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \
        kubernetes_host="https://$KUBERNETES_PORT_443_TCP_ADDR:443" \
        kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt

2.6、检查当前配置

# 确认有chat-test path
vault secrets list
# 确认有chat-ai的policy
vault policy  list
# 确认有k8s auth
vault auth list
# list key,之前提到过,命令查询不需要增加data path
vault kv list chat-test/backend/chat-ai

2.7、创建k8s认证角色(Role)

names是k8s服务用的serviceaccount,一定要填写服务所在的namespace,最后关联之前创建的策略

vault write auth/kubernetes/role/chat-ai-role \
    bound_service_account_names=vault-chat-ai \
    bound_service_account_namespaces=chat-test \
    policies=chat-ai-policy 

查看role和读取role,确认符合预期

vault list auth/kubernetes/role
vault read auth/kubernetes/role/chat-ai-role

三、k8s应用使用接入vault

3.1、创建ServiceAccount

cat <<EOF|kubectl create -f -
apiVersion: v1
kind: ServiceAccount
metadata:
  name: vault-chat-ai  # ServiceAccount应用中要配置
  namespace: chat-test  
EOF

3.2、静态配置

annotions中添加如下配置,agent-inject: "true"允许vault agent注入(vault-agent容器替我们去和vault-server交互),vault.hashicorp.com/role: chat-ai-role使用我们2.7中创建好的Role,通过Role去vault中获取相关权限,另外三个配置是secret的静态配置

        vault.hashicorp.com/agent-inject: "true"
        vault.hashicorp.com/agent-inject-secret-gemini-pro-1: chat-test/data/backend/chat-ai/gemini-pro_1
        vault.hashicorp.com/agent-inject-secret-gemini-pro-2: chat-test/data/backend/chat-ai/gemini-pro_2
        vault.hashicorp.com/agent-inject-secret-han10: chat-test/data/backend/chat-ai/han10
        vault.hashicorp.com/role: chat-ai-role
        # 和containers同级
      serviceAccountName: vault-chat-ai  # 使用指定的 ServiceAccount

我遇到过的报错基本都是403,ServiceAccount没有创建,valult policy path错误,Role绑定policy错误。如果这些没有问题,静态配置读取应该是正常的,可以从容器中获取到secret

/app # ls /vault/secrets/
gemini-pro-1  gemini-pro-2  han10         han4

静态配置一笔带过,静态配置的弊端较多,不适合业务广泛使用

3.3、动态配置

回顾一下vault secret配置的流程,vault中创建策略后使用一个Role去绑定,配置在指定ns下,Deployment通过annotions启用了vault agent的注入,指定role的名称和ServiceAccount,这两个操作是让agent可以通过容器ServiceAccount和role获取到正确的权限。权限的获取是通过读取k8s的ServiceAccount Token(JWT),代码中的Client初始化操作同样是指定JWT位置和Vault Role
前提是应用先配置好正确的ServiceAccount,这个环节是程序获取JWT的关键步骤

~# kubectl logs -f -n chat-test sijia-test-6776985f9b-47s64 
2024/09/04 01:14:31 密钥:chat-test/data/backend/chat-ai/gemini-pro_1, 值:map[token:yAXbBljmJjvR0]
package main

import (
    "fmt"
    "log"
    "net/http"
    "os"

    vaultApi "github.com/hashicorp/vault/api"
)

var (
    vaultHost        = "vault.vault.svc:8200"                                // Vault 服务器地址
    vaultRole        = "chat-ai-role"                                        // 程序需要 vault role + jwt 登录
    vaultSecretsPath = "chat-test/data/backend/chat-ai/gemini-pro_1"         // v2 kv会自动添加data路径,命令查询和curl不需要加data,但是api需要
    vaultJWTPath     = "/var/run/secrets/kubernetes.io/serviceaccount/token" // JWT 的路径
)

func main() {
    config := vaultApi.DefaultConfig()
    config.Address = fmt.Sprintf("http://%s", vaultHost)

    client, err := vaultApi.NewClient(config)
    if err != nil {
        log.Fatalf("创建 Vault 客户端错误:%s", err)
    }

    jwt, err := os.ReadFile(vaultJWTPath)
    if err != nil {
        log.Fatalf("读取 JWT 错误:%s", err)
    }

    options := map[string]interface{}{
        "jwt":  string(jwt),
        "role": vaultRole,
    }
    loginSecret, err := client.Logical().Write("auth/kubernetes/login", options)
    if err != nil {
        log.Fatalf("登录 Vault 错误:%s", err)
    }
    client.SetToken(loginSecret.Auth.ClientToken)

    // 打印特定密钥的值
    printKey(client)

    log.Fatal(http.ListenAndServe(":8080", nil))
}

// 修改此函数以打印特定密钥
func printKey(client *vaultApi.Client) {
    secret, err := client.Logical().Read(vaultSecretsPath)
    if err != nil {
        log.Printf("读取密钥错误:%s", err)
        return
    }
    log.Printf("密钥:%s, 值:%v\n", vaultSecretsPath, secret.Data["data"])
}


我尝试获取chat-test/data/backend/chat-ai/下的所有key时出现了错误,暂时无法解决

  • 13
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值