全部產品
Search
文件中心

Key Management Service:ACK通過ack-kms-agent-webhook-injector組件快速整合KMS Agent

更新時間:Jul 19, 2025

本文介紹阿里雲ACK如何通過ack-kms-agent-webhook-injector組件整合KMS Agent。

概述

架構說明

阿里雲Container Service for Kubernetes (ACK)提供了ack-kms-agent-webhook-injector組件,通過配置特定註解(kms-agent-webhook-injector/inject),將KMS Agent作為Sidecar容器注入Pod,從而允許容器中的應用通過本地HTTP介面,藉助KMS Agent從KMS執行個體擷取憑據並緩衝在記憶體中,避免敏感資訊寫入程式碼,保障資料安全。此外,KMS Agent 的緩衝機制,能提升高並發及網路不穩定情境下訪問KMS的穩定性,最佳化使用體驗。架構圖如下所示:

使用限制

  • ACK叢集類型限制:支援ACK託管與專有叢集ACK Serverless叢集

  • 地區限制:ACK叢集與KMS執行個體需要在同一地區。

  • 效能限制:由於每個Pod獨立運行KMS Agent Sidercar容器,若業務部署大量Pod,在身分識別驗證過程中如果STS Token請求每分鐘超過500次則會觸發限流,進而對KMS Agent的正常工作產生影響。

費用說明

  • KMS側的費用:

    • 計費方式為訂用帳戶:使用KMS Agent前您需要購買KMS執行個體,使用KMS Agent本身不會額外收取您的費用。詳細介紹,請參見訂用帳戶

    • 計費方式為隨用隨付:除您已產生的費用外,使用KMS Agent擷取憑據時,會因API調用請求產生額外的QPS調用費用。詳細介紹,請參見隨用隨付

  • ACK側的費用:

    ack-kms-agent-webhook-injector組件本身是完全免費的,但使用該組件的過程中,可能會產生額外的費用。

    • 安裝ack-kms-agent-webhook-injector組件後,會產生一個Webhook服務工作負載,該負載將佔用一定計算資源併產生費用,您可以在設定檔中,對該負載的 CPU 和記憶體使用量進行限制。

    • 當您建立或更新合格工作負載時,ack-kms-agent-webhook-injector將會把KMS Agent以 Sidecar的形式注入到您的容器中,KMS Agent會使用一定計算資源併產生費用。

身份認證方式

支援RRSA和Worker RAM角色兩種認證方式。

  • (推薦)方式一:RRSA方式

    可以實現Pod維度許可權隔離。適用於1.22及以上版本的ACK託管叢集ACK Serverless叢集

    重要

    實現Pod維度許可權隔離需要ack-kms-agent-webhook-injector的版本大於等於v0.2.0。

    例如,通過RRSA方式,您可以讓部署在app1-dev中的應用,使用app1-rrsa這個角色,訪問KMS中帶有secret:app1標籤的憑據,。部署在app2-dev中的應用,使用app2-rrsa這個角色,訪問KMS中帶有secret:app2標籤的憑據。

  • 方式二:Worker RAM角色方式

    可以實現Worker維度許可權隔離。由於ACK Serverless叢集不支援綁定Worker RAM角色,該方式只適用於ACK託管叢集ACK專有叢集

    例如,您可以通過Worker RAM角色,設定app1-dev和app2-dev中的應用訪問KMS中管理的憑據。同一個Worker裡的應用使用同一個Worker RAM角色,即您無法針對app1-dev和app2-dev設定不同的許可權。

前提條件

步驟一:配置認證資訊,使KMS Agent可以訪問特定的憑據

方式一:RRSA方式授權

以您有兩個應用app1和app2為例,app1需要訪問KMS執行個體中打標籤secret: app1的憑據,app2需要訪問KMS執行個體中打標籤secret: app2的憑據。

  • app1的NameSpace為app1-dev,serviceAccountName為app1-service,RAM角色為app1-rrsa。

  • app2的NameSpace為app2-dev,serviceAccountName為app2-service,RAM角色為app2-rrsa。

  1. 啟用ACK RRSA功能。

    建立叢集時開啟

    建立ACK託管叢集ACK Edge叢集時,您可以在叢集配置的進階選項(選填)地區,選中開啟RRSA功能。

    image

    在叢集資訊頁面開啟

    1. 登入Container Service管理主控台,在左側導覽列選擇叢集列表

    2. 叢集列表頁面,單擊目的地組群名稱,然後在左側導覽列,選擇叢集資訊

    3. 基本資料頁簽的安全與審計地區,單擊RRSA OIDC右側的開啟image

    4. 在彈出的啟用RRSA對話方塊,單擊確定

      基本資料地區,當叢集狀態由更新中變為運行中後,表明該叢集的RRSA特性已變更完成。

  2. 開啟叢集詳情頁,在基本資料頁簽的安全與審計地區,將滑鼠懸浮至RRSA OIDC右側已開啟上面,查看供應商的URL連結和ARN資訊。image

  3. 為app1建立一個可信實體為身份供應商的RAM角色,並授權其可以訪問標籤為secret: app1的憑據。具體操作,請參見建立可信實體為身份供應商的RAM角色

    1. 登入RAM控制台

    2. 在左側導覽列,選擇身份管理 > 角色,然後在角色頁面,單擊建立角色

    3. 建立角色面板,選擇可信實體類型為身份供應商,並單擊切換編輯器image

    4. 建立角色頁面的可視化編輯,配置如下角色資訊後,單擊確定

      配置項

      描述

      效果

      選擇允許

      主體

      選擇身份供應商。

      • 身份供應商類型:選擇OIDC。

      • 身份供應商:開啟RRSA後,ACK叢集會預設建立身份供應商,命名格式為ack-rrsa-<cluster_id>。其中,<cluster_id>為您的叢集ID。

      操作

      保持預設。即勾選sts:AssumeRole。

      條件

      在預設的oidc:issoidc:aud限制條件基礎上,新增一個限制條件:

      • 條件鍵:選擇oidc:sub

      • 運算子:選擇StringEquals

      • 條件值:system:serviceaccount:<namespace>:<serviceAccountName>。本文樣本為system:serviceaccount:app1-dev:app1-service

        • <namespace>:希望注入KMS Agent的應用所在的命名空間。

        • <serviceAccountName>:希望注入KMS Agent的應用所使用的服務賬戶名稱。服務賬戶名稱為Pod提供身份標識,可以通過RRSA機制與RAM角色動態綁定。

        說明

        若您需要為多個不同命名空間中的不同服務賬戶配置,您可以配置多個值。

    5. 建立角色對話方塊中,設定角色名稱,然後單擊確定。本文角色名稱以app1-rrsa為例。

    6. (可選)查看app1-rrsa這個RAM角色的信任策略。

      信任策略表示允許服務賬戶app1-service通過阿里雲RRSA(RAM Roles for Service Accounts) ,在滿足OIDC身分識別驗證條件後,擔任某個RAM角色。image

    7. 建立權限原則。

      權限原則名稱以app1-rrsa-kms-policy為例,策略內容為訪問標籤為secret: app1的憑據。image

      {
          "Version": "1",
          "Statement": [
              {
                  "Effect": "Allow",
                  "Action": [
                      "kms:Decrypt",
                      "kms:GetSecretValue"
                  ],
                  "Resource": "*",
                  "Condition": {
                      "StringEqualsIgnoreCase": {
                          "kms:tag/secret": [
                              "app1"
                          ]
                      }
                  }
              }
          ]
      }
    8. 將app1-rrsa-kms-policy權限原則,授權給app1-rrsa角色。具體操作,請參見為RAM角色授權image

  4. 為app2建立一個可信實體為身份供應商的RAM角色,並授權其可以訪問標籤為secret: app1的憑據。具體操作,請參見建立可信實體為身份供應商的RAM角色

    1. 登入RAM控制台

    2. 在左側導覽列,選擇身份管理 > 角色,然後在角色頁面,單擊建立角色

    3. 建立角色面板,選擇可信實體類型為身份供應商,並單擊切換編輯器

    4. 建立角色頁面的可視化編輯,配置如下角色資訊後,單擊確定

      配置項

      描述

      效果

      選擇允許

      主體

      選擇身份供應商。

      • 身份供應商類型:選擇OIDC。

      • 身份供應商:開啟RRSA後,ACK叢集會預設建立身份供應商,命名格式為ack-rrsa-<cluster_id>。其中,<cluster_id>為您的叢集ID。

      操作

      保持預設。即勾選sts:AssumeRole。

      條件

      在預設的oidc:issoidc:aud限制條件基礎上,新增一個限制條件:

      • 條件鍵:選擇oidc:sub

      • 運算子:選擇StringEquals

      • 條件值:system:serviceaccount:<namespace>:<serviceAccountName>。本文樣本為system:serviceaccount:app2-dev:app2-service

        • <namespace>:希望注入KMS Agent的應用所在的命名空間。

        • <serviceAccountName>:希望注入KMS Agent的應用所使用的服務賬戶名稱。服務賬戶名稱為Pod提供身份標識,可以通過RRSA機制與RAM角色動態綁定。

        說明

        若您需要為多個不同命名空間中的不同服務賬戶配置,您可以配置多個值。

    5. 建立角色對話方塊中,設定角色名稱,然後單擊確定。本文角色名稱以app2-rrsa為例。

    6. (可選)查看app2-rrsa這個RAM角色的信任策略。

      信任策略表示允許服務賬戶app2-service通過阿里雲RRSA(RAM Roles for Service Accounts) ,在滿足OIDC身分識別驗證條件後,擔任某個RAM角色。

    7. 建立權限原則。

      權限原則名稱以app2-rrsa-kms-policy為例,策略內容為訪問標籤為secret: app2的憑據。

      {
          "Version": "1",
          "Statement": [
              {
                  "Effect": "Allow",
                  "Action": [
                      "kms:Decrypt",
                      "kms:GetSecretValue"
                  ],
                  "Resource": "*",
                  "Condition": {
                      "StringEqualsIgnoreCase": {
                          "kms:tag/secret": [
                              "app2"
                          ]
                      }
                  }
              }
          ]
      }
    8. 將app2-rrsa-kms-policy權限原則,授權給app2-rrsa角色。具體操作,請參見為RAM角色授權

方式二:Worker RAM角色方式授權

Worker RAM角色屬於普通服務角色,您可以為Worker節點池指定預設角色自訂角色

  • 預設角色:ACK託管叢集會自動建立一個所有節點共用的預設Worker RAM角色。當您通過預設的Worker RAM角色授權時,許可權將會共用給叢集內所有的節點,可能會存在非預期的許可權擴散的風險。

  • (推薦)自訂角色:需要您提前在存取控制台建立一個普通服務角色,並為節點池指定這個角色。通過為不同的節點池分配特定的角色,可以將每個節點池的許可權隔離開,降低叢集內所有節點共用相同許可權的風險。本文以使用自訂角色為例。

  1. 建立普通服務角色。角色名稱以ack-secret-manager為例。

    1. 登入RAM控制台,在左側導覽列,選擇身份管理 > 角色

    2. 角色頁面,單擊建立角色

    3. 建立角色頁面,選擇信任主體類型雲端服務信任主體名稱請選擇Container ServiceKubernetes版,最後單擊確定image

    4. 建立角色對話方塊,輸入角色名稱,單擊確定

  2. 建立如下自訂權限原則。策略名稱稱以ack-secret-manager-policy為例。具體操作,請參見建立自訂權限原則image

    {
      "Version": "1",
      "Statement": [
        {
          "Action": [
            "kms:GetSecretValue",
            "kms:Decrypt"
          ],
          "Resource": [
            "*"
          ],
          "Effect": "Allow"
        }
      ]
    }
  3. 將自訂權限原則ack-secret-manager-policy,授權給自訂角色ack-secret-manager。image

  4. 建立節點池時,為節點池指定Worker RAM角色。

    Worker RAM角色選擇自訂,然後選擇ack-secret-managerimage

    建立叢集時同步建立節點池,詳細介紹,請參見建立ACK託管叢集建立ACK Serverless叢集。為已有叢集建立節點池,請參見建立和管理節點池

步驟二:在ACK叢集中建立Namespace和Service Account

Namespace將ACK叢集劃分為邏輯隔離的虛擬空間,用於區分開發、測試、生產等環境,不同Namespace中的應用預設無法互訪資源,為RAM角色綁定提供物理邊界。

分別建立兩個Namespace,app1-dev和app2-dev。對應的serviceAccountName分別為app1-service和app2-service。

  1. 通過YAML檔案建立名為app1-dev的Namespace。

    1. 建立YAML,檔案名稱以app1-namespace.yaml為例。

      apiVersion: v1
      kind: Namespace
      metadata:
        name: app1-dev
    2. 執行如下命令在ACK叢集中建立名為 app1-dev 的 Namespace。

      kubectl apply -f app1-namespace.yaml
    3. 查看Namespace是否建立成功。

      kubectl get namespaces

      若輸出包含app1-dev,即代表建立成功。

  2. 通過YAML檔案建立名為app1-service的Service Account。

    1. 建立YAML,檔案名稱以app1-serviceaccount.yaml為例。

      apiVersion: v1
      kind: ServiceAccount
      metadata:
        name: app1-service
        namespace: app1-dev
    2. 執行如下命令在 app1-dev 中建立一個名為 app1-service 的ServiceAccount。

      kubectl apply -f app1-serviceaccount.yaml
    3. 查看 ServiceAccount 是否建立成功。

      kubectl get serviceaccount -n app1-dev

      若輸出包含 app1-service,即代表建立成功。

  3. 重複上述操作,建立命名空間app2-dev和服務賬戶app2-service。

步驟三:在ACK叢集中安裝ack-kms-agent-webhook-injector組件

  1. 登入Container Service管理主控台,在左側導覽列選擇叢集列表

  2. 叢集列表頁面,單擊目的地組群名稱,然後在左側導覽列,選擇應用 > Helm

  3. Helm頁面,單擊建立,配置基本資料,單擊下一步

    配置項

    說明

    應用程式名稱

    請輸入您的應用程式名稱。建議您使用預設應用程式名稱,此處無需輸入,單擊下一步後會提示使用預設應用程式名稱ack-kms-agent-webhook-injector,選擇是即可。

    命名空間

    選擇應用所在的命名空間。建議您使用預設命名空間,此處無需選擇保持預設,單擊下一步後會提示使用預設命名空間kube-system,選擇是即可。

    一個ACK叢集中安裝到一個命名空間即可,無需重複安裝。

    來源

    預設為應用市場,不支援修改。

    Chart

    搜尋並選中ack-kms-agent-webhook-injector。

  4. 在彈出的對話方塊中確認無誤後,單擊

    選擇是即使用預設安裝路徑,組件預設安裝在kube-system命名空間中,並以組件名稱發布應用。image

  5. 參數配置頁面,完成各項配置,然後單擊確定

    配置項

    說明

    Chart 版本

    建議您選擇最新版本。

    參數

    agent.auth.roleArn和agent.auth.roleArnMapping參數配置說明:

    • 採用RRSA方式授權時:agent.auth.roleArn為空白,agent.auth.roleArnMapping格式為<NameSpace>:<serviceAccountName>:<RAM Role ARN>

      agent:
        auth:
          roleArn: 
          roleArnMapping:
            app1-dev:app1-service: acs:ram::190325303126****:role/app1-rrsa
            app2-dev:app2-service: acs:ram::190325303126****:role/app2-rrsa
    • 採用Worker RAM角色方式授權:agent.auth.roleArn和agent.auth.roleArnMapping均設定為空白。

  6. 請等待約30 秒,進入命名空間(預設為kube-system),在無狀態工作負載中檢查組件狀態,確認組件是否已經就緒。

步驟四:為工作負載注入KMS Agent

通過配置註解將KMS Agent注入Pod,根據您是為建立工作負載的Pod還是為現存工作負載的Pod進行配置,具體操作不同。

以無狀態工作負載Deployment為例。本文樣本中,請為app1-dev和app2-dev分別執行如下操作,進行設定。

  1. 登入Container Service管理主控台

  2. 進入到目的地組群,在左側導覽列選擇工作負載 > 無狀態

  3. 建立或修改工作負載時,為添加如下Pod註解:名稱kms-agent-webhook-injector/inject為true(除此之外,填入值1,T,t,True,TRUE亦可視為有效值)。

    情境一:通過鏡像建立工作負載

    單擊使用鏡像建立,完成各項配置,單擊建立

    填寫進階配置時,請在標籤與註解地區,添加如下Pod註解:名稱填入kms-agent-webhook-injector/inject填入true。image

    情境二:通過 YAML 建立工作負載

    單擊使用YAML建立資源,完成各項配置,單擊建立

    編輯 YAML 檔案中的 spec.template.metadata.annotations 一項(若不存在,您需要自主建立),向其中添加索引值 kms-agent-webhook-injector/inject: "true"。其他參數如何配置,請參見工作負載YAML樣本image.png

    情境三:現存工作負載

    1. 定位到目標工作負載,單擊操作列的詳情

    2. 在工作負載詳情頁,單擊右上方的YAML編輯按鈕,尋找到 spec.template.metadata.annotations 一項(若不存在,您需要自主建立),向其中添加索引值kms-agent-webhook-injector/inject: "true"image.png

    3. 單擊更新,等待工作負載重新就緒。

  4. 返回無狀態頁面,進入工作負載,可以觀察到容器組頁簽下各個容器,在鏡像列即可看到 KMS Agent 已經作為 Sidecar被注入到您的工作負載中。

    重要

    容器組頁簽下查看鏡像時,您可能注意到Pod被注入了兩次KMS Agent鏡像,這是因為我們使用了初始化容器(initContainer)來進行必要的初始化工作,該初始化容器在初始化完成後就會終止(Terminated),不會對您的應用產生負面影響,也不會持續佔用您的計算資源。

    image

  5. 如果您使用RRSA方式授權,請修改YAML設定檔,將serviceAccountName參數改為步驟一中授權的服務賬戶。

    本文樣本為app1-service和app2-service。修改後該應用和KMS Agent能被正確授權訪問KMS憑據。

    說明

    Worker RAM角色方式授權時無需修改設定檔,服務賬戶可自動扮演Worker RAM角色擷取許可權。

步驟五:在應用程式容器中擷取 KMS 憑據

當工作負載注入了KMS Agent後,您可在應用程式容器中,通過HTTP協議向KMS Agent發起請求,擷取儲存在KMS中的憑據。

樣本說明:

  • 本地主機HTTP連接埠號碼取預設值2025,如果您設定為其他連接埠號碼,請將樣本中的2025修改為實際使用連接埠號碼。

  • token路徑取預設路徑file:///var/run/kmstoken/token,如果您設定其他路徑,例如file:///var/run/path1/path2,請將樣本中的/var/run/kmstoken/token替換為/var/run/path1/path2

KMS Agent預設擷取憑據的ACSCurrent 版本。要擷取其他版本的憑據值,您可以設定 versionStage 或 versionId

重要

KMS Agent只監聽127.0.0.1,即僅允許同一台機器上的應用或進程與其通訊,外部網路裝置無法串連。訪問地址僅支援localhost或127.0.0.1,不支援改為應用的本地IP。以下樣本以localhost為例。

使用curl

實際使用時請將範例程式碼中的<SecretId>替換為您實際的憑據名稱。

 # 從檔案讀取 token
 curl -v -H "X-KMS-Token:$(</var/run/kmstoken/token)" 'http://localhost:2025/secretsmanager/get?secretId=<SecretId>'
 
 # 直接寫 token
 curl -v -H "X-KMS-Token:<token>" 'http://localhost:2025/secretsmanager/get?secretId=<SecretId>'

您可以指定versionStage 或 versionId以擷取特定憑據值。以擷取指定versionId的憑據值為例,使用時請將0a7513ee719da740807b15b77500****替換為您實際的憑據版本。

 # 從檔案讀取 token
 curl -v -H "X-KMS-Token:$(</var/run/kmstoken/token)" 'http://localhost:2025/secretsmanager/get?secretId=<SecretId>&versionId=0a7513ee719da740807b15b77500****'
 
 # 直接寫 token
 curl -v -H "X-KMS-Token:<token>" 'http://localhost:2025/secretsmanager/get?secretId=<SecretId>&versionId=0a7513ee719da740807b15b77500****'

Go程式碼範例

使用時請將範例程式碼中的agent-test替換為您實際的憑據名稱。

package main

import (
	"fmt"
	"io/ioutil"
	"net/http"
)

func main() {
	
	//支援指定versionStage或versionId以擷取特定憑據值。
	//以擷取指定versionId的憑據值為例,url := fmt.Sprintf("http://localhost:2025/secretsmanager/get?secretId=%s&versionId=%s", "agent-test", "version-id")。
	url := fmt.Sprintf("http://localhost:2025/secretsmanager/get?secretId=%s", "agent-test")

	token, err := ioutil.ReadFile("/var/run/kmstoken/token")
	if err != nil {
		fmt.Printf("error reading token file: %v\n", err)
	}

	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		fmt.Printf("error creating request: %v\n", err)
	}

	req.Header.Add("X-KMS-Token", string(token))

	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		fmt.Printf("error sending request: %v \n", err)
	}
	defer resp.Body.Close()

	body, _ := ioutil.ReadAll(resp.Body)
	fmt.Printf("status code %d - %s \n", resp.StatusCode, string(body))
}