使用Vault作为KMS服务

Vault是一个基于身份的密钥管理和数据加密系统,提供对Token、密码、证书、API Key等常见敏感凭据的安全存储和控制,可有效解决应用系统中对敏感信息的硬编码问题。本文介绍如何在ACK Serverless集群中部署和使用Vault。

前提条件

  • 已创建ACK Serverless集群,且集群为1.22及以上版本。具体操作,请参见创建集群手动升级集群

  • 已安装Helm,且Helm为v3.6及以上版本。更多信息,请参见Helm Release

安装初始化Vault

步骤一:安装Vault

  1. 任选以下方式获取Vault的安装包。本文示例中Helm的Chart版本为vault-0.24.1,Vault版本为1.3.1。

    • 登录Git仓库vault-helm获取。

    • 通过远程仓库获取。执行以下命令,添加并更新仓库。

      helm repo add hashicorp https://helm.releases.hashicorp.com
      helm repo update
  2. 执行以下命令,配置别名(Alias)简化操作命令。

    以下脚本代码以ACK Serverless集群的KubeConfig信息放置在$HOME/Downloads/kubeconfig文件中为例说明,使用时KubeConfig位置信息请根据实际位置替换。

    # Helm客户端。
    alias h="helm --kubeconfig $HOME/Downloads/kubeconfig"
    # kubectl客户端。
    alias k="kubectl --kubeconfig $HOME/Downloads/kubeconfig"
  3. 安装Vault。

    生产环境建议安装Raft版本的Vault,请勿使用Standard alone版本。

    1. 执行以下命令,创建名为vault的命名空间。

      k create ns vault

      将Vault安装在vault的命名空间中,后续和K8s Namespace相关的值都为vault

    2. 执行以下命令,在名为vault的命名空间中安装Vault。

      以下StorageClass(SC)使用ACK支持的SC,可通过k get sc 查询。存储的Size不小于20 GiB。部署完成后,将生成三个按量付费的ESSD云盘,作为Vault的Pod挂载使用的PV。关于云盘的计费信息,请参见计费

      h install vault -nvault hashicorp/vault \
          --set='server.ha.enabled=true' \
          --set='server.ha.raft.enabled=true' \
          --set='server.dataStorage.size=20Gi' \
          --set='server.dataStorage.storageClass=alicloud-disk-essd'
    3. 执行以下命令,查看Vault的Pod状态。

      k get po -n vault

      预期输出:

      NAME                                    READY   STATUS    RESTARTS   AGE
      vault-0                                 0/1     Running   0          45s
      vault-1                                 0/1     Running   0          45s
      vault-2                                 0/1     Running   0          44s
      vault-agent-injector-59fdd7cdf8-prwv7   1/1     Running   0          45s

步骤二:初始化和解封Vault

  1. 执行以下命令,查看Vault第一次启动后的状态。

    k exec -nvault vault-0 -- vault status

    预期输出:

    Key                Value
    ---                -----
    Seal Type          shamir
    Initialized        false
    Sealed             true
    Total Shares       0
    Threshold          0
    Unseal Progress    0/0
    Unseal Nonce       n/a
    Version            1.13.1
    Build Date         2023-03-23T12:51:35Z
    Storage Type       raft
    HA Enabled         true
    command terminated with exit code 2

    InitializedfalseSealedtrue时,表明Vault未进行初始化,且没有解封(Unseal)。您需要进行后续的初始化和解封操作。

  2. 执行以下命令,初始化Vault。

    通过容器内的Vault二进制,生成Key。

    k exec vault-0 -nvault -- vault operator init -key-shares=5 -key-threshold=3 -format=json > cluster-keys.json

    初始化过程中,系统生成了5个shares,并指定解封次数threshold为3。

  3. 在实际生产环境中,在Pod内通过POST的vault-0.vault.vault.svc:8200/sys/init 进行OpenAPI调用。

    此处使用了StatefulSet(sts)的DNS解析习惯,即{$podName}.{$stsName}.{$Namespace}.svc。关于初始化Vault对应的OpenAPI,请参见Vault Start Initialization

    展开查看生成的文件cluster-keys.json的内容(OpenAPI返回的data类似)

    {
      "unseal_keys_b64": [
        "Zu6EdLIFn+2****",
        "yvBur6WEphY****",
        "HR4hNkQN++h****",
        "85jAjs8xAj4****",
        "DgYQhjo6l14****"
      ],
      "unseal_keys_hex": [
        "66ee8474b****",
        "caf06eafa****",
        "1d1e21364****",
        "f398c08ec****",
        "0e0610863****"
      ],
      "unseal_shares": 5,
      "unseal_threshold": 3,
      "recovery_keys_b64": [],
      "recovery_keys_hex": [],
      "recovery_keys_shares": 0,
      "recovery_keys_threshold": 0,
      "root_token": "hvs.5aiXKN****"
    }

    将以上生成的文件cluster-keys.json中unseal_keys_b64的内容导出,进行下一步解封操作。

  4. 执行以下命令,解封Vault。关于解封Vault对应的OpenAPI,请参见Vault Unseal

    由于以上unseal_threshold设置为3,所以此处需选取3个Unseal key进行解封,分别执行1次,共需执行3次。

    k exec -nvault vault-0 -- vault operator unseal Zu6EdLIFn+2****
  5. 执行以下命令,查看vault-0的状态。

    k exec -it vault-0 -n vault -- vault status
    Key                     Value
    ---                     -----
    Seal Type               shamir
    Initialized             true
    Sealed                  false
    Total Shares            5
    Threshold               3
    Version                 1.13.1
    Build Date              2023-03-23T12:51:35Z
    Storage Type            raft
    Cluster Name            vault-cluster-504959a1
    Cluster ID              d99594a5-75de-53fa-59dd-19ed024b****
    HA Enabled              true
    HA Cluster              https://vault-0.vault-internal:8201
    HA Mode                 active
    Active Since            2023-05-06T10:30:38.237415781Z
    Raft Committed Index    36
    Raft Applied Index      36
    
    k get po -n vault
    NAME                                    READY   STATUS    RESTARTS   AGE
    vault-0                                 1/1     Running   0          46m
    vault-1                                 0/1     Running   0          46m
    vault-2                                 0/1     Running   0          46m
    vault-agent-injector-59fdd7cdf8-prwv7   1/1     Running   0          46m

    预期输出表明,vault-0已初始化完成。

  6. (可选)如需查看Raft节点,可通过root Token登录节点进行查看。

    1. 执行以下命令,登录vault-0节点。

      此处root_toke值为hvs.5aiXKN****,其值可通过步骤3生成的文件cluster-keys.json获取。

      k exec vault-0 -n vault -- vault login hvs.5aiXKN****
    2. 执行以下命令,查看Raft节点。

      k exec -nvault vault-0 -- vault operator raft list-peers

      预期输出:

      Node                                    Address                        State     Voter
      ----                                    -------                        -----     -----
      10285056-839a-f306-a301-5024934a794f    vault-0.vault-internal:8201    leader    true

步骤三:添加其他Vault节点

  1. 执行以下命令,添加Vault节点。关于添加Vault节点的OpenAPI,请参见Raft

    k exec -nvault vault-1  -- vault operator raft join http://vault-0.vault-internal:8200
    Key       Value
    ---       -----
    Joined    true
    k exec -nvault vault-2  -- vault operator raft join http://vault-0.vault-internal:8200
    Key       Value
    ---       -----
    Joined    true
  2. 分别执行以下命令,解封添加的Vault节点。

    每个节点至少要用不同的Unseal key执行3次,共需执行6次。

    k exec -nvault vault-1 -- vault operator unseal Zu6EdLIF****
    k exec -nvault vault-2 -- vault operator unseal Zu6EdLIF****
    ...
    k exec -nvault vault-1 -- vault operator unseal DgYQhjo6****
    k exec -nvault vault-2 -- vault operator unseal DgYQhjo6****
  3. 执行以下命令,查看节点添加结果。

    k exec -n vault vault-0 -- vault operator raft list-peers

    预期输出:

    Node                                    Address                        State       Voter
    ----                                    -------                        -----       -----
    10285056-839a-f306-a301-5024934a794f    vault-0.vault-internal:8201    leader      true
    71ffd98c-d6d4-a7b3-994c-9ce87f464486    vault-1.vault-internal:8201    follower    true
    1e9f37dc-b55b-fc46-8ca8-595428ad1d81    vault-2.vault-internal:8201    follower    true
    
    k get po -n vault
    NAME                                    READY   STATUS    RESTARTS   AGE
    vault-0                                 1/1     Running   0          66m
    vault-1                                 1/1     Running   0          66m
    vault-2                                 1/1     Running   0          66m
    vault-agent-injector-59fdd7cdf8-prwv7   1/1     Running   0          66m

    预期输出表明,vault-1vault-2节点已添加成功。

使用示例

示例一:通过Vault管理Kubernetes集群的ServiceAccount Token

您可以通过Valut获取rolebinding clusterrolebinding对应的Token。启用此特性后,在Kubernetes集群上binding时,将不会生成对应的Secret。此方式通过Vault获取访问APIServer的Bear Token,可避免攻击者通过Kubernetes集群直接获取SA的访问凭证。

  1. 使用以下YAML内容,分别创建ClusterRole.yaml和ClusterRoleBinding.yaml文件。

    展开查看ClusterRole.yaml文件

    apiVersion: rbac.authorization.k8s.io/v1
    kind: ClusterRole
    metadata:
      name: k8s-minimal-secrets-abilities
    rules:
    - apiGroups: [""]
      resources: ["serviceaccounts/token"]
      verbs: ["create"]

    展开查看ClusterRoleBinding.yaml文件

    apiVersion: rbac.authorization.k8s.io/v1
    kind: ClusterRoleBinding
    metadata:
      name: vault-token-creator-binding
    roleRef:
      apiGroup: rbac.authorization.k8s.io
      kind: ClusterRole
      name: k8s-minimal-secrets-abilities
    subjects:
    - kind: ServiceAccount
      name: vault
      namespace: vault
  2. 执行以下命令,为Vault的SA绑定ClusterRole,使其能创建SA的Token。

    k apply -f ClusterRole.yaml
    k apply -f ClusterRoleBinding.yaml
  3. 执行以下命令,开启Vault的Kubernetes的Secret特性。

    k exec -nvault vault-0 -- vault secrets enable kubernetes
  4. 验证使用效果。

    1. 执行以下命令,创建名为test的命名空间。

      k create ns test
    2. 使用以下YAML内容,创建test.yaml文件。

      展开查看test.yaml文件

      apiVersion: v1
      kind: ServiceAccount
      metadata:
        name: test-service-account-with-generated-token
        namespace: test
      ---
      apiVersion: rbac.authorization.k8s.io/v1
      kind: Role
      metadata:
        name: test-role-list-pods
        namespace: test
      rules:
      - apiGroups: [""]
        resources: ["pods"]
        verbs: ["list"]
      ---
      apiVersion: rbac.authorization.k8s.io/v1
      kind: RoleBinding
      metadata:
        name: test-role-abilities
        namespace: test
      roleRef:
        apiGroup: rbac.authorization.k8s.io
        kind: Role
        name: test-role-list-pods
      subjects:
      - kind: ServiceAccount
        name: test-service-account-with-generated-token
        namespace: test
    3. 执行以下命令,部署生成测试的SA Role RoleBinding。

      k apply -f test.yaml
    4. 执行以下命令,获取Token信息。关于OpenAPI的更多信息,请参见Kubernetes API

      k exec -nvault vault-0 -- vault write -f kubernetes/config
      k exec -nvault vault-0 -- vault write kubernetes/roles/my-role allowed_kubernetes_namespaces="*" service_account_name="test-service-account-with-generated-token" token_default_ttl="10m"
      k exec -nvault vault-0 -- vault write kubernetes/creds/my-role kubernetes_namespace=test

      最后一个命令输出的一个JWT Token,可以用于请求访问APIserver。

      JWT Token即为如下代码中的service_account_token字段。

      Key                          Value
      ---                          -----
      lease_id                     kubernetes/creds/my-role/XPDLbuXJ0Bt4fF****
      lease_duration               10m
      lease_renewable              false
      service_account_name         test-service-account-with-generated-token
      service_account_namespace    test
      service_account_token        eyJhbGciOiJSUzI1NiIsImtp****
    5. 执行以下命令,访问APIServer。

      curl -sk https://XX.XX.XX.XX:6443/api/v1/namespaces/test/pods --header "Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtp****"
        "kind": "PodList",
        "apiVersion": "v1",
        "metadata": {
          "resourceVersion": "2861371"
        },
        "items": []
      }

      以上Token有效期是10 min,如果Token过期,需要调用Write操作重新获取Token。

      k exec -nvault vault-0 -- vault write kubernetes/creds/my-role kubernetes_namespace=test

示例二:如何在应用Pod中动态获取RAM凭证

您可以通过Vault存储访问阿里云RAM用户的AK和SK信息。应用通过和Vault交互动态获取相关凭证。关于更多OpenAPI信息,请参见AliCloud Secrets Engine

  1. 执行以下命令,为Vault开启Alicloud的Secret特性。

    k exec -nvault vault-0 -- vault secrets enable  alicloud
  2. 使用阿里云账号登录RAM控制台

  3. 使用以下权限策略内容,创建自定义权限策略ExampleRAMPolicyforVault。具体操作,请参见创建自定义权限策略

    该权限策略允许在RAM用户使用任何类型的角色创建、删除凭证或策略,并为用户分配策略,允许取消用户的某个策略,创建和删除用户、通过角色扮演来访问资源等。

    展开查看自定义权限策略ExampleRAMPolicyforVault

    {
      "Statement": [
        {
          "Action": [
            "ram:CreateAccessKey",
            "ram:DeleteAccessKey",
            "ram:CreatePolicy",
            "ram:DeletePolicy",
            "ram:AttachPolicyToUser",
            "ram:DetachPolicyFromUser",
            "ram:CreateUser",
            "ram:DeleteUser",
            "sts:AssumeRole"
          ],
          "Effect": "Allow",
          "Resource": "*"
        }
      ],
      "Version": "1"
    }
  4. 创建RAM用户hashicorp-vault。具体操作,请参见创建RAM用户

  5. 为RAM用户hashicorp-vault授予自定义权限策略ExampleRAMPolicyforVault。具体操作,请参见为RAM用户授权

  6. 为RAM用户hashicorp-vault创建AccessKey。具体操作,请参见创建AccessKey

    记录此处的AK和SK信息。例如,此处的AccessKey为ak1,SecretKey为sk1。

  7. 执行以下命令,将已获取的AK和SK信息写入Vault。

    k exec -nvault vault-0 -- vault write alicloud/config access_key=ak1 secret_key=sk
    Success! Data written to: alicloud/config

    AK和SK会存储在Vault每个节点的/vault/data/vault.db文件中,同时此文件会持久化到PV中,所以节点重启后信息不会丢失。

  8. 将Remote和Inline策略定义写入Vault。

    • 执行以下命令,将Remote策略写入Vault。

      Remote模式指写入RAM中已存在的权限策略类型和名称。此处写入一个自定义权限策略ExampleRAMPolicyforVault,两个系统策略AliyunOSSReadOnlyAccess和AliyunRDSReadOnlyAccess。

      k exec -nvault vault-0 -- vault write alicloud/role/policy-based \
          remote_policies='name:ExampleRAMPolicyforVault,type:Custom' \
          remote_policies='name:AliyunOSSReadOnlyAccess,type:System' \
          remote_policies='name:AliyunRDSReadOnlyAccess,type:System'
      Success! Data written to: alicloud/role/policy-based
    • 执行以下命令,将Inline策略写入Vault。

      Inline模式指直接在API请求中写入策略模板。此处可将已生成的自定义权限策略ExampleRAMPolicyforVault的配置写入Vault。

      k exec -nvault vault-0 -- vault write alicloud/role/policy-based \
          inline_policies=-<<EOF
      [
      {
        "Statement": [
          {
            "Action": [
              "ram:CreateAccessKey",
              "ram:DeleteAccessKey",
              "ram:CreatePolicy",
              "ram:DeletePolicy",
              "ram:AttachPolicyToUser",
              "ram:DetachPolicyFromUser",
              "ram:CreateUser",
              "ram:DeleteUser",
              "sts:AssumeRole"
            ],
            "Effect": "Allow",
            "Resource": "*"
          }
        ],
        "Version": "1"
      }
      ]
      EOF
  9. 创建RAM角色vaultTestRole并为该RAM角色授予自定义权限策略ExampleRAMPolicyforVault。具体操作,请参见创建可信实体为阿里云账号的RAM角色为RAM角色授权

    为角色授权完成后,会生成一条ARN记录,格式如下,其中15261****为RAM用户的ID。

    vaultTestRole@role.15261****.onaliyunservice.com
  10. 执行以下命令,将对应的ARN信息写入Vault,即将绑定关系写入Vault,实现Vault对RAM角色vaultTestRole的扮演。

    k exec -nvault vault-0 -- vault write alicloud/role/role-based \
          role_arn='acs:ram::15261****:role/vaultTestRole'

验证使用效果

  1. 执行以下命令,获取基于策略的访问凭证。

    k exec -nvault vault-0 -- vault read alicloud/creds/policy-based
    Key                Value
    ---                -----
    lease_id           alicloud/creds/policy-based/TG1isE6uga94sRv60NK7****
    lease_duration     768h
    lease_renewable    true
    access_key         ak1
    secret_key         sk1
  2. 执行以下命令,获取基于角色的访问凭证(STS Token)。

    k exec -nvault vault-0 -- vault read alicloud/creds/role-based
    Key                Value
    ---                -----
    lease_id           alicloud/creds/role-based/uJxVwNSnqzcni75kkf****
    lease_duration     59m59s
    lease_renewable    false
    access_key         STS.NUM2e1BrC****
    expiration         2023-05-09T04:16:17Z
    secret_key         3VmmRy****
    security_token     CAISiwJ1q****

角色权限说明

如果一个角色使用不同的权限策略,就需要为角色分配不同的权限点。

使用的权限策略

对应分配的权限点

inline_policies

remote_policies

role_arn

示例三:如何在应用Pod中通过RAM认证访问Vault API

在K8s的应用Pod中,可通过写代码访问Vault的服务,使用此方式对接阿里云的身份认证、以及身份对应角色信息的查询。此应用代码携带身份认证的信息,通过访问Vault的OpenAPI(Auth、Alicloud、Login)获取该用户身份对应的角色信息,以及访问Vault的Token。

使用此Token可对身份对应的角色进行操作,例如,查看角色详情、角色列表、创建角色和删除角色。关于更多操作,请参见AliCloud Auth Method (API)

  1. 执行以下命令,为Vault开启Alicloud的Auth。更多OpenAPI信息,请参见Enable Auth Method

    k exec -nvault vault-0 -- vault auth enable alicloud
  2. 执行以下命令,将对应的ARN写入Vault。

    k exec -nvault vault-0 -- vault write auth/alicloud/role/vaultTestRole arn='acs:ram::15261****:role/vaulttestrole'
  3. 获取调用GetCallerIdentity接口使用的URL和Header。具体操作,请参见vault-plugin

    应用代码需先调用GetCallerIdentity接口,然后获取调用该接口使用的URL和Header。关于GetCallerIdentity接口调用,请参见GetCallerIdentity OpenAPI。进入调用页面,在左侧搜索框输入GetCallerIdentity,在中间区域选择自身业务所在Region,然后在右侧选择SDK示例,即可看到对应的代码。

  4. 调用Login。其中IDENTITY_REQUEST_URL_BASE_64为URL的Base64编码,IDENTITY_REQUEST_HEADERS_BASE_64为Header的Base64编码。更多信息,请参见Login OpenAPI

  5. 应用程序通过上一步Login调用返回的client_token字段,调用Vault的AuthAlicloud等OpenAPI,实现对应角色的访问。

常见问题

Vault是第三方的维护的开源项目,当您遇到的问题非阿里云或ACK官方提供支持的产品或组件,请前往Vault Communit开源项目社区咨询处理。

如何修复Vault集群异常?

如果Vault Pod重启,Pod会进入0/1 Running状态。您可以参考以下步骤对Vault集群异常问题进行修复。

  1. 执行以下命令,查看Pod的状态。

    k exec -nvault vault-0 -- vault status

    预期输出:

    Key                Value
    ---                -----
    Seal Type          shamir
    Initialized        true
    Sealed             true
    Total Shares       5
    Threshold          3
    Unseal Progress    0/3
    Unseal Nonce       n/a
    Version            1.13.1
    Build Date         2023-03-23T12:51:35Z
    Storage Type       raft
    HA Enabled         true
    command terminated with exit code 2

    预期输出表明,Pod又处于sealed状态。需要重新进行解封操作。

  2. 执行以下命令,解封Vault节点。

    此处仍需选择3个Unseal Key分别执行1次,共需执行3次。

    k exec -nvault vault-0 -- vault operator unseal Zu6EdL****
  3. 执行以下命令,通过root Token登录节点,查看Raft列表。

    k exec -nvault vault-0 -- vault operator raft list-peers

    预期输出:

    Node                                    Address                        State       Voter
    ----                                    -------                        -----       -----
    10285056-839a-f306-a301-5024934a794f    vault-0.vault-internal:8201    follower    true
    71ffd98c-d6d4-a7b3-994c-9ce87f464486    vault-1.vault-internal:8201    leader      true
    1e9f37dc-b55b-fc46-8ca8-595428ad1d81    vault-2.vault-internal:8201    follower    true

    预期输出表明,vault-0状态变为follower正常状态。

如何在ACK Serverless集群中通过Service调用Vault?

Vault安装完成后,将生成多个Service。您可以通过以下命令,查看具体Service信息。

k get svc -n vault

预期输出:

NAME                       TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)             AGE
vault                      ClusterIP   172.16.193.219   <none>        8200/TCP,8201/TCP   47h
vault-active               ClusterIP   172.16.177.54    <none>        8200/TCP,8201/TCP   47h
vault-internal             ClusterIP   None             <none>        8200/TCP,8201/TCP   47h
vault-standby              ClusterIP   172.16.29.54     <none>        8200/TCP,8201/TCP   47h
  • vaultvault-internal为整个Vault集群节点的负载均衡,其中,vault-internal为Headless的SVC。

  • vault-active为Raft选出的leader节点。

  • vault-standby为Raft中的follower节点。