Dapr 入門教程之密鑰存儲

應用程序通常通過使用專用的 Secret 存儲來存儲敏感信息,如密鑰和 Token,用於與數據庫、服務和外部系統進行身份驗證的 Secret 等。通常這需要涉及到設置一個 Secret 存儲,如 Azure Key VaultHashicorp Vault 等,並在那裏存儲應用程序級別的私密數據。爲了訪問這些 Secret 存儲,應用程序需要導入 Secret 存儲的 SDK,並使用它來訪問私密數據,這可能需要相當數量的代碼,這些代碼與應用程序的實際業務領域無關,因此在可能使用不同供應商特定的 Secret 存儲的多雲場景中,這將成爲更大的挑戰。

爲了使開發者更容易使用應用程序的私密數據,Dapr 有一個專門的 Secret 構建塊 API,允許開發者從 Secret 存儲中獲取私密數據。使用 Dapr 的 Secret 存儲構建塊通常涉及以下內容。

默認情況下,Dapr 在 Kubernetes 模式下通過 Helm 或 dapr init -k 部署的時候,啓用一個內置的 Kubernetes Secret 存儲,如果你使用另一個 Secret 存儲,你可以使用 disable-builtin-k8s-secret-store 設置禁用 Dapr Kubernetes Secret 存儲。

應用程序代碼可以調用 Secret 構建塊 API 從 Dapr 支持的 Secret 存儲中檢索私密數據,這些 Secret 存儲可以在你的代碼中使用。例如,下圖顯示了一個應用程序從配置的雲 Secret 存儲庫中的一個名爲 vault 的 Secret 存儲庫中請求名爲 mysecret 的私密數據。

應用程序可以使用 secrets API 來訪問來自 Kubernetes Secret 存儲的私密數據。在下面的例子中,應用程序從 Kubernetes Secret 存儲中檢索相同的 mysecret

本地環境使用 Secrets

同樣我們以 quickstarts 倉庫進行說明。

git clone [-b <dapr_version_tag>] https://github.com/dapr/quickstarts.git
cd quickstarts

然後定位到 secretstore 目錄下面的 node 文件夾:

cd tutorials/secretstore/node

app.js 中是一個簡單的 Express 應用,它暴露了一些路由和處理程序,我們可以先查看下該文件中的如下內容:

const daprPort = process.env.DAPR_HTTP_PORT || 3500;
const secretStoreName = process.env.SECRET_STORE;
const secretName = "mysecret";

其中 secretStoreName 從環境變量 SECRET_STORE 中讀取,,其爲 Kubernetes 部署注入了值 kubernetes,對於本地開發,環境變量必須設置爲 localsecretstore 值。

然後我們看看 getsecret 處理程序代碼:

app.get("/getsecret"(_req, res) ={
  const url = `${secretsUrl}/${secretStoreName}/${secretName}?metadata.namespace=default`;
  console.log("Fetching URL: %s", url);
  fetch(url)
    .then((res) => res.json())
    .then((json) ={
      let secretBuffer = new Buffer(json["mysecret"]);
      let encodedSecret = secretBuffer.toString("base64");
      console.log("Base64 encoded secret is: %s", encodedSecret);
      return res.send(encodedSecret);
    });
});

該代碼從 secret store 中獲取名爲 mysecret 的數據,並顯示該數據的 Base64 編碼數據。

我們在 secrets.json 文件中添加一個 mysecret 的 Secret 數據:

{
  "mysecret""abcd"
}

同樣我們也需要添加一個 Secret 對應的 Component 組件,比如在本地自拓管模式,創建一個如下所示的配置文件:

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: localsecretstore
  namespace: default
spec:
  type: secretstores.local.file
  version: v1
  metadata:
    - name: secretsFile
      value: secrets.json
    - name: nestedSeparator
      value: ":"

上面的組件定義了一個本地 Secret 存儲庫,其 Secret 文件路徑爲 secrets.json 文件。

其中 Secret 存儲 JSON 的路徑是與你調用 dapr run 的位置相關的。

然後我們需要將上面的 Secret Store 名稱設置爲環境變量:

# nux/Mac OS:
export SECRET_STORE="localsecretstore"

# Windows:
set SECRET_STORE=localsecretstore

接下來我們爲 Node 應用安裝依賴:

npm install # yarn

然後我們使用 Dapr 帶上本地的 secret store 組件運行 Node 應用:

$ dapr run --app-id nodeapp --components-path ./components --app-port 3000 --dapr-http-port 3500 node app.js
ℹ️  Starting Dapr with id nodeapp. HTTP Port: 3500. gRPC Port: 58744
INFO[0000] starting Dapr Runtime -- version 1.8.4 -- commit 18575823c74318c811d6cd6f57ffac76d5debe93  app_id=nodeapp instance=MBP2022.local scope=dapr.runtime type=log ver=1.8.4
# ......
INFO[0000] component loaded. name: localsecretstore, type: secretstores.local.file/v1  app_id=nodeapp instance=MBP2022.local scope=dapr.runtime type=log ver=1.8.4
INFO[0000] all outstanding components processed          app_id=nodeapp instance=MBP2022.local scope=dapr.runtime type=log ver=1.8.4
# ......
== APP == Node App listening on port 3000!
INFO[0000] application discovered on port 3000           app_id=nodeapp instance=MBP2022.local scope=dapr.runtime type=log ver=1.8.4
WARN[0000] [DEPRECATION NOTICE] Adding a default content type to incoming service invocation requests is deprecated and will be removed in the future. See https://docs.dapr.io/operations/support/support-preview-features/ for more details. You can opt into the new behavior today by setting the configuration option `ServiceInvocation.NoDefaultContentType` to true.  app_id=nodeapp instance=MBP2022.local scope=dapr.runtime type=log ver=1.8.4
INFO[0000] application configuration loaded              app_id=nodeapp instance=MBP2022.local scope=dapr.runtime type=log ver=1.8.4
INFO[0000] actors: state store is not configured - this is okay for clients but services with hosted actors will fail to initialize!  app_id=nodeapp instance=MBP2022.local scope=dapr.runtime type=log ver=1.8.4
INFO[0000] actor runtime started. actor idle timeout: 1h0m0s. actor scan interval: 30s  app_id=nodeapp instance=MBP2022.local scope=dapr.runtime.actor type=log ver=1.8.4
INFO[0000] dapr initialized. Status: Running. Init Elapsed 326.57000000000005ms  app_id=nodeapp instance=MBP2022.local scope=dapr.runtime type=log ver=1.8.4
INFO[0000] placement tables updated, version: 0          app_id=nodeapp instance=MBP2022.local scope=dapr.runtime.actor.internal.placement type=log ver=1.8.4
ℹ️  Updating metadata for app command: node app.js
✅  You're up and running! Both Dapr and your app logs will appear here.

啓動後我們可以使用 dapr list 來查看應用列表:

$ dapr list
  APP ID   HTTP PORT  GRPC PORT  APP PORT  COMMAND      AGE  CREATED              PID
  nodeapp  3500       58744      3000      node app.js  11m  2022-09-27 15:13.46  5906

啓動完成後我們可以直接訪問應用的 getsecret 接口:

$ curl -k http://localhost:3000/getsecret

正常輸出結果是 YWJjZA==,也就是上面的 abcd 做了 base64 編碼後的值。

然後觀察應用的日誌會出現類似於如下所示的內容:

== APP == Fetching URL: http://localhost:3500/v1.0/secrets/localsecretstore/mysecret?metadata.namespace=default

== APP == Base64 encoded secret is: YWJjZA==

測試完成後可以使用 dapr stop 命令來停止應用:

dapr stop --app-id nodeapp

Kubernetes 環境使用 Secrets

接下來我們來了解下在 Kubernetes 模式下 Dapr 是如何使用 Secrets store 的,當然首先需要在 Kubernetes 集羣中安裝 Dapr 控制平面。

Dapr 可以使用許多不同的 secret stores 來解析 secrets 數據,比如 AWS Secret ManagerAzure Key VaultGCP Secret ManagerKubernetes 等,我們這裏可以直接使用 Kubernetes 的 Secret 對象進行演示。

首先講 secrets 數據添加到 ./mysecret 文件,比如你的密碼是 abcd,則 ./mysecret 文件內容應該就是 abcd

然後基於 ./mysecret 文件創建一個 Kubernetes Secret 對象:

$ kubectl create secret generic mysecret --from-file ./mysecret

注意創建的 Secret 對象的名稱 mysecret,後面會使用到。

創建完成後我們可以查看下該對象中的數據是否符合預期:

$ kubectl get secret mysecret -o yaml
apiVersion: v1
data:
  mysecret: YWJjZAo=
kind: Secret
metadata:
  creationTimestamp: "2022-09-27T07:34:31Z"
  name: mysecret
  namespace: default
  resourceVersion: "5133821"
  uid: c9aa573c-5f71-439c-b482-748ac0fe3ae7
type: Opaque

接下來我們就可以部署 Node.js 應用到 Kubernetes 集羣中,對應的資源清單文件如下所示:

kind: Service
apiVersion: v1
metadata:
  name: nodeapp
  labels:
    app: node
spec:
  selector:
    app: node
  ports:
    - protocol: TCP
      port: 80
      targetPort: 3000
  type: LoadBalancer

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nodeapp
  labels:
    app: node
spec:
  selector:
    matchLabels:
      app: node
  template:
    metadata:
      labels:
        app: node
      annotations:
        dapr.io/enabled: "true"
        dapr.io/app-id: "nodeapp"
        dapr.io/app-port: "3000"
    spec:
      containers:
        - name: node
          image: ghcr.io/dapr/samples/secretstorenode:latest
          env:
            - name: SECRET_STORE
              value: "kubernetes"
          ports:
            - containerPort: 3000
          imagePullPolicy: Always

這裏的核心重點是需要我們配置環境變量 SECRET_STORE,將其值設置爲 kubernetes,這樣我們的應用就知道應該通過 Kubernetes 獲取 Secret 數據了。直接部署該應用即可:

$ kubectl apply -f deploy/node.yaml
$ kubectl get pods
NAME                                    READY   STATUS    RESTARTS       AGE
nodeapp-6cb5b689cf-vtn74                2/2     Running   0              92
$ kubectl get svc
NAME                        TYPE           CLUSTER-IP       EXTERNAL-IP    PORT(S)                               AGE
nodeapp                     LoadBalancer   10.101.216.73    192.168.0.50   80:32719/TCP                          13d
nodeapp-dapr                ClusterIP      None             <none>         80/TCP,50001/TCP,50002/TCP,9090/TCP   13d

部署完成後我們這裏可以通過 192.168.0.50 這個 EXTERNAL-IP 訪問到應用:

curl -k http://192.168.0.50/getsecret

正常上面的請求輸出結果爲 YWJjZAo=,也可以查看 Node 應用日誌:

$ kubectl logs --selector=app=node -c node
Node App listening on port 3000!
Fetching URL: http://localhost:3500/v1.0/secrets/kubernetes/mysecret?metadata.namespace=default
Base64 encoded secret is: YWJjZAo=

從上面日誌可以看出 Node 應用程序正在向 dapr 發出請求,以便從 secret store 獲取 secret 數據,注意其中的 mysecret 是上面創建的 Secret 對象名稱。

當然如果你使用的是其他 secret store,比如 HashiCorp Vault 則需要創建一個對應的 Component 組件了,類型爲secretstores.hashicorp.vault,如下所示的資源清單:

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: vault
spec:
  type: secretstores.hashicorp.vault
  version: v1
  metadata:
  - name: vaultAddr
    value: [vault_address] # Optional. Default: "https://127.0.0.1:8200"
  - name: caCert # Optional. This or caPath or caPem
    value: "[ca_cert]"
  - name: caPath # Optional. This or CaCert or caPem
    value: "[path_to_ca_cert_file]"
  - name: caPem # Optional. This or CaCert or CaPath
    value : "[encoded_ca_cert_pem]"
  - name: skipVerify # Optional. Default: false
    value : "[skip_tls_verification]"
  - name: tlsServerName # Optional.
    value : "[tls_config_server_name]"
  - name: vaultTokenMountPath # Required if vaultToken not provided. Path to token file.
    value : "[path_to_file_containing_token]"
  - name: vaultToken # Required if vaultTokenMountPath not provided. Token value.
    value : "[path_to_file_containing_token]"
  - name: vaultKVPrefix # Optional. Default: "dapr"
    value : "[vault_prefix]"
  - name: vaultKVUsePrefix # Optional. default: "true"
    value: "[true/false]"
  - name: enginePath # Optional. default: "secret"
    value: "secret"
  - name: vaultValueType # Optional. default: "map"
    value: "map"

對於其他 Dapr 支持的 secret store 的配置屬性可以參考官方文檔 https://docs.dapr.io/reference/components-reference/supported-secret-stores/ 瞭解相關信息。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/4nlI2qtN0khUUxe4Mg1u6w