Experience Dapr - Secret Store


在這年代不管是開發單體應用架構、還是分散式系統,不管怎樣的語言、平台,都要處理這個問題:

機敏性資料,如 資料庫連線、第三方串接的 API Token

這些東西不管在哪個年代都要處理,在這個 DevOps、DevSecOps、經常有 資安事件 的年代,實際的解決方案就顯得更重要了,從 Key Management、Secret Management 都是很重要的過程。其實三大公有雲也都有對應的解決方案,像是 AWS Secret Manager, Azure Key Vault, GCP Secret Manager … 經過一些時日,對於 Dapr 的基本概念與實際架構有了初步認識之後,第一個我想了解的就是 Dapr 如何處理 Secret Manager 這件事情?

上一篇整理了 Dapr 概念與設計,本文整理 Dapr 如何透過 Building Block,實際整合各種 Secret Store 的方法,以及實際要注意的事項。


基本概念

在 Dapr 使用 Secret Management 的基本概念如下圖,圖中我標示了使用的次序性:

  • (1): Dapr Sidecar 起來後,依據 component 的設定,初始化對應的 secret type.
  • (2): Dapr App 初始化後,透過 Dapr API 取得需要的 secret key / value,然後使用。

圖中上方的 Cloud Secret Stores,就是 Dapr 支援的種類,像是 LocalFile, Environment, HashiCorp Vault, Kubernetes secret, AWS Secret Manager, … 等,詳細列表參閱 Secret store component specs

除了這些功能,另外 Dapr 也支援 Secret scoping 的概念,也就是透過 Dapr Config 決定可以存取的範圍。

基本概念不難懂,來看看實際怎麼使用的過程。


使用 Secret Store

在本機: 開發階段

定義 component,名稱為 webapi-secrets-store 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: webapi-secrets-store
namespace: default
spec:
type: secretstores.local.file
version: v1
metadata:
- name: secretsFile
value: ./components/webapi_secrets-file.json
- name: nestedSeparator
value: ":"

設定中指定檔案的形式,檔名 webapi_secrets-file.json ,內容如下:

1
2
3
4
5
{
"payment-access-token": "Qoo@-234s-qewr-dfae",
"aws-ak": "AKIAUTGELESZ3NEI",
"aws-sk": "1234567890123456789"
}

整個目錄結構:

1
2
3
4
5
6
~$ tree
.
├── README.md
└── components
├── webapi_local-secret.yaml
└── webapi_secrets-file.json

接下使用 Dapr CLI 驗證

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
## 啟動一個空的 Dapr App
dapr run --app-id webapi \
--dapr-http-port 3500 \
--components-path ./components


## 驗證: Single
❯ curl http://localhost:3500/v1.0/secrets/webapi-secrets-store/payment-access-token
{"payment-access-token":"Qoo@-234s-qewr-dfae"}

❯ curl http://localhost:3500/v1.0/secrets/webapi-secrets-store/aws-ak
{"aws-ak":"AKIAUTGELESZ3NEI"}

❯ curl http://localhost:3500/v1.0/secrets/webapi-secrets-store/aws-sk
{"aws-sk":"1234567890123456789"}

## 全都要: Bulk
❯ curl http://localhost:3500/v1.0/secrets/webapi-secrets-store/bulk
{"payment-access-token":{"payment-access-token":"Qoo@-234s-qewr-dfae"},"aws-ak":{"aws-ak":"AKIAUTGELESZ3NEI"},"aws-sk":{"aws-sk":"1234567890123456789"}}

同樣的流程,再增加一個名為 aws-secret-store,type 是 secretstores.aws.secretmanager 的 component:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: aws-secret-store
namespace: default
spec:
type: secretstores.aws.secretmanager
version: v1
metadata:
- name: region
value: "mars-asimov-1"
- name: accessKey
value: "ASDFGHJKLASDFGHJKL"
- name: secretKey
value: "asdfghjkl;1234567890-ertyujikldfvbgnm"

我在 AWS Secret Manager 已經先建立好一個 Key/Value 的 Secret,名稱是 webapi。同樣使用 Dapr CLI 測試:

1
2
curl http://localhost:3500/v1.0/secrets/aws-secret-store/webapi
{"webapi":"{\"db-connection \":\"server=192.168.353.102;user id=root;password=qoo;port=3306;database=test;\",\"cache-connection\":\"10.211.14.639:6379\",\"sso-google-client-id\":\"123456789-xxxxxxxxxx.apps.googleusercontent.com\",\"sso-google-client-secret\":\"ZXCVBNM<SDFGHJKL\"}"}

Dapr Secrets API 的結構是這樣的:

單一: GET http://localhost:<daprPort>/v1.0/secrets/<secret-store-name>/<name>
全部: GET http://localhost:<daprPort>/v1.0/secrets/<secret-store-name>/bulk

詳細規格參閱 Dapr API: Secrets API Reference

Secret Scoping

幾個常見的使用情境 (Scenarios):

  • Scenario 1: Deny access to all secrets for a secret store
    • 禁止 App 存取 K8s 的 Secret
  • Scenario 2: Allow access to only certain secrets in a secret store
    • 只允許存取 secret store 裡的特定的 keys
    • 正向表列、白名單列舉
  • Scenario 3: Deny access to certain sensitive secrets in a secret store
    • 跟 Scenario 2 相反,允許全部,但是特定的 key 不允許存取

官方文件 很完整的說明這三個主要情境的應用範例,同時列舉了 Permission Priority 列表。


問題與思考

思考取得 Secret 的途徑

在這篇文章:系統發生異常時,第一時間如何快速止血? 提到一個重要的思路:

服務邊界與依賴性:把每個 service 的 downstream / upstream or dependency (外部依賴) 搞清楚

同樣的,要知道前面簡單的 Lab 過程中,上下游的關係,也就是:

  1. 誰需要發起取得 Secret 的請求?
  2. 誰負責去 Secret Store (這個例子是檔案) 取得 Secret Values?

回來看看啟動 Dapr APP 的 Log:

1
2
3
4
5
INFO[0000] Initialized name resolution to standalone     app_id=webapi instance=iStar.local scope=dapr.runtime type=log ver=1.1.2
INFO[0000] component loaded. name: aws-secret-store, type: secretstores.aws.secretmanager/v1 app_id=webapi instance=iStar.local scope=dapr.runtime type=log ver=1.1.2
INFO[0000] waiting for all outstanding components to be processed app_id=webapi instance=iStar.local scope=dapr.runtime type=log ver=1.1.2
INFO[0000] component loaded. name: webapi-secrets-store, type: secretstores.local.file/v1 app_id=webapi instance=iStar.local scope=dapr.runtime type=log ver=1.1.2
INFO[0000] all outstanding components processed app_id=webapi instance=iStar.local scope=dapr.runtime type=log ver=1.1.2

這段訊息代表 Dapr Sidecar 已經 初始化 好 Component 相關東西,上面可以看到有我定義好的 aws-secret-store, webapi-secrets-store

思考的點:

Dapr Sidecar 初始化做了哪一些事?這些是只做一次嗎?

有興趣,可以去爬各種實作的 Source Code, 我爬了以下的部分:

從 Code 不難了解,其實是每次都會直接操作的。了解這要做什麼?往下看。

[TBD] 成本問題:如何降低對外部 Secret Store / Manager 的存取次數

上一個思考,要帶出的問題:

每個 pod 起來時,至少 Dapr Sidecar 就要對 secret store 存取一次,所以大量啟動 pod 時都會對 secret store 多次存取?

實際上,目前 Dapr 只有 Access Control 的設計:How-To: Limit the secrets that can be read from secret stores,但是沒有針對 Secret Access Rate 有對應的控制方式。

這算是尚未滿足需求的。

我想像的是:

  1. Dapr App 在 K8s 上如果起 10 個 pod
  2. 每個 Pod 啟動後,Sidecar 都會依據 Component 設定,根 Secret Store 取得資訊,放在 memory
  3. 如果有一個 Pod 重啟了,那他的 Sidecar 應該會去問其他 Sidecar 是否已經有 Component 的資訊,從中取得現況。

這個過程應該會需要 分散式共識演算法 處理這些資料的 複本同步問題 (Replication),也就是 Raft 演算法當中的複製狀態機 (Replicate State Machine, RSM)。實際上 Dapr 的 Placement 裡也有使用 Hashicorp 的 Consul Raft library,但怎麼處理官方文件並沒有細講,但 Source Code 裡有很多線索。這些是我在 Source code: dapr/pkg/placement/raft/ 找到的資訊,需要更近一步的理解。

Dapr 在 AWS 使用上的整合

熟悉 AWS 的人都知道 AWS 有很完整的權限控制概念,可以完全 控制反轉 (Inversion of Control, IoC) 控制權,也就是:

機敏資料的控制權,原本在開發者手上,反轉到 AWS 系統管理員

相關的機制有:

最好的實踐,就是利用既有可靠的機制,所以 Dapr 官方文件的 Integration 部分,針對 Cloud Providers 提供了 Authenticating to AWS 的完整說明。

Audit Log

如果是使用 AWS Secret Management,那就返回 AWS Cloudtrail 應該就可以。

方便的代價:支援 local environment variables 很方便,都使用這個就好了?

官方支援 local env 的模式,但 文件 也警告以下訊息:

我個人建議就是不要使用,方便的代價很高,試著打這個 API 就可以拿到所有 env vars:

http://localhost:<daprPort>/v1.0/secrets/<secret-store-name>/bulk

Building Block 怎麼使用 Secret Store?

Secret Store 可以儲存像是 database connection string 這種機敏性資訊,但是其他的 Dapr components 也都有機敏性資料,像是 state store, binding 都是需要這些資訊才能建立連線,那要怎麼使用 secret store 呢?

參閱官方文件:How-To: Reference secrets in components,下一篇筆記會整理實際的心得。


結語

一開始我在想用詞的問題,到底是要用 Secret Store or Secret Management?

官方文件寫的 Secret Store,我會認為只是一個 Storage 的介接 (Adaptor),換言之,並沒有完整考慮 Lifecycle,換言之對於怎麼 Management 並沒有太多著墨。而現階段 Dapr 的設計,稱得上 Management 是 Scoping / Rate Limit 的部分,基本的功能就是處理 Storage 的介接。

知道怎麼用 Secret Store 了,下一步讓應用程式起來通常要處理的就是資料。Dapr 是分散式框架,必須面對的就是 Message Queue,其中 Pub/Sub 是最常見的模式。下一篇整理 Dapr Pub/Sub Component 的基本使用。


延伸閱讀

Dapr 系列文章

分散式系統系列文章

站內文章

Dapr 官方文件




Comments