Dapr 安全性之訪問控制策略
安全是 Dapr 的基礎,本文我們將來說明在分佈式應用中使用 Dapr 時的安全特性和能力,主要可以分爲以下幾個方面。
-
與服務調用和
pub/sub
APIs 的安全通信。 -
組件上的安全策略並通過配置進行應用。
-
運維操作安全實踐。
-
狀態安全,專注於靜態的數據。
Dapr 通過服務調用 API 提供端到端的安全性,能夠使用 Dapr 對應用程序進行身份驗證並設置端點訪問策略。
服務調用範圍訪問策略
跨命名空間的服務調用
Dapr 應用程序可以被限定在特定的命名空間,以實現部署和安全,當然我們仍然可以在部署到不同命名空間的服務之間進行調用。默認情況下,服務調用支持通過簡單地引用應用 ID (比如 nodeapp
) 來調用同一命名空間內的服務:
localhost:3500/v1.0/invoke/nodeapp/method/neworder
服務調用還支持跨命名空間的調用,在所有受支持的託管平臺上,Dapr 應用程序 ID 符合包含目標命名空間的有效 FQDN
格式,可以同時指定:
-
應用 ID (如
nodeapp
) -
應用程序運行的命名空間(
production
)。
比如在 production
命名空間中的 nodeapp
應用上調用 neworder
方法,則可以使用下面的方式:
localhost:3500/v1.0/invoke/nodeapp.production/method/neworder
當使用服務調用在命名空間中調用應用程序時,我們可以使用命名空間對其進行限定,特別在 Kubernetes 集羣中的跨命名空間調用是非常有用的。
爲服務調用應用訪問控制列表配置
訪問控制策略在配置文件中被指定,並被應用於被調用應用程序的 Dapr sidecar,對被調用應用程序的訪問是基於匹配的策略動作,你可以爲所有調用應用程序提供一個默認的全局動作,如果沒有指定訪問控制策略,默認行爲是允許所有調用應用程序訪問被調用的應用程序。
在具體學習訪問控制策略配置之前,我們需要先了解兩個概念:
-
TrustDomain
- “信任域” 是管理信任關係的邏輯組。每個應用程序都分配有一個信任域,可以在訪問控制列表策略規範中指定。如果未定義策略規範或指定了空的信任域,則使用默認值public
,該信任域用於在 TLS 證書中生成應用程序的身份。 -
App Identity
- Dapr 請求 sentry 服務爲所有應用程序生成一個 SPIFFE id,這個 id 附加在 TLS 證書中。SPIFFE id 的格式爲:spiffe://<trustdomain>/ns/<namespace>/<appid>
,對於匹配策略,調用應用的信任域、命名空間和應用 ID 值從調用應用的 TLS 證書中的SPIFFE id
中提取,這些值與策略規範中指定的信任域、命名空間和應用 ID 值相匹配。如果這三個都匹配,則更具體的策略將進一步匹配。
訪問控制策略會遵循如下所示的一些規則:
-
如果未指定訪問策略,則默認行爲是允許所有應用訪問被調用應用上的所有方法
-
如果未指定全局默認操作且未定義應用程序特定策略,則將空訪問策略視爲未指定訪問策略,並且默認行爲是允許所有應用程序訪問被調用應用程序上的所有方法
-
如果未指定全局默認操作,但已定義了一些特定於應用程序的策略,則會採用更安全的選項,即假設全局默認操作拒絕訪問被調用應用程序上的所有方法
-
如果定義了訪問策略並且無法驗證傳入的應用程序憑據,則全局默認操作將生效
-
如果傳入應用的信任域或命名空間與應用策略中指定的值不匹配,則應用策略將被忽略並且全局默認操作生效
下面是一些使用訪問控制列表進行服務調用的示例場景。
場景 1:拒絕所有應用程序的訪問,除非 trustDomain = public
、namespace = default
、appId = app1
,使用如下所示的配置,允許所有 appId = app1
的調用方法,並拒絕來自其他應用程序的所有其他調用請求。
apiVersion: dapr.io/v1alpha1
kind: Configuration
metadata:
name: appconfig
spec:
accessControl:
defaultAction: deny
trustDomain: "public"
policies:
- appId: app1
defaultAction: allow
trustDomain: "public"
namespace: "default"
場景 2:拒絕訪問除 trustDomain = public
、namespace = default
、appId = app1
、operation = op1
之外的所有應用程序,使用此配置僅允許來自 appId = app1
的方法 op1
,並且拒絕來自所有其他應用程序的所有其他方法請求,包括 app1
上的其他方法。
apiVersion: dapr.io/v1alpha1
kind: Configuration
metadata:
name: appconfig
spec:
accessControl:
defaultAction: deny
trustDomain: "public"
policies:
- appId: app1
defaultAction: deny
trustDomain: "public"
namespace: "default"
operations:
- name: /op1
httpVerb: ["*"]
action: allow
場景 3:拒絕對所有應用程序的訪問,除非 HTTP 的特定 verb 和 GRPC 的操作匹配,使用如下所示的配置,僅允許以下場景訪問,並且來自所有其他應用程序的所有其他方法請求(包括 app1
或 app2
上的其他方法)都會被拒絕。
-
trustDomain = public、namespace = default、appID = app1、operation = op1、http verb = POST/PUT
-
trustDomain = “myDomain”、namespace = “ns1”、appID = app2、operation = op2 並且應用程序協議是 GRPC,僅允許來自
appId = app1
的方法op1
上的 POST/PUT 請求以及來自所有其他應用程序的所有其他方法請求,包括app1
上的其他方法,被拒絕
apiVersion: dapr.io/v1alpha1
kind: Configuration
metadata:
name: appconfig
spec:
accessControl:
defaultAction: deny
trustDomain: "public"
policies:
- appId: app1
defaultAction: deny
trustDomain: "public"
namespace: "default"
operations:
- name: /op1
httpVerb: ["POST", "PUT"]
action: allow
- appId: app2
defaultAction: deny
trustDomain: "myDomain"
namespace: "ns1"
operations:
- name: /op2
action: allow
場景 4:允許訪問除 trustDomain = public
、namespace = default
、appId = app1
、operation = /op1/*
所有 http verb 之外的所有方法。
apiVersion: dapr.io/v1alpha1
kind: Configuration
metadata:
name: appconfig
spec:
accessControl:
defaultAction: allow
trustDomain: "public"
policies:
- appId: app1
defaultAction: allow
trustDomain: "public"
namespace: "default"
operations:
- name: /op1/*
httpVerb: ["*"]
action: deny
場景 5:允許訪問 trustDomain = public
、namespace = ns1
、appId = app1
的所有方法並拒絕訪問 trustDomain = public
、namespace = ns2
、appId = app1
的所有方法,此場景展示瞭如何指定具有相同應用 ID 但屬於不同命名空間的應用。
apiVersion: dapr.io/v1alpha1
kind: Configuration
metadata:
name: appconfig
spec:
accessControl:
defaultAction: allow
trustDomain: "public"
policies:
- appId: app1
defaultAction: allow
trustDomain: "public"
namespace: "ns1"
- appId: app1
defaultAction: deny
trustDomain: "public"
namespace: "ns2"
場景 6:允許訪問除 trustDomain = public
、namespace = default
、appId = app1
、operation = /op1/**/a
、所有 http 動詞之外的所有方法。
apiVersion: dapr.io/v1alpha1
kind: Configuration
metadata:
name: appconfig
spec:
accessControl:
defaultAction: allow
trustDomain: "public"
policies:
- appId: app1
defaultAction: allow
trustDomain: "public"
namespace: "default"
operations:
- name: /op1/**/a
httpVerb: ["*"]
action: deny
下面我們通過一個具體的示例來展示下訪問控制策略的使用,同樣還是使用 quickstarts
示例中的 hello-world
進行說明。
git clone [-b <dapr_version_tag>] https://github.com/dapr/quickstarts.git
cd quickstarts/tutorials/hello-world/node
該示例應用中包含一個 python 應用去調用一個 node.js 應用程序,訪問控制列表依靠 Dapr Sentry 服務來生成帶有 SPIFFE id
的 TLS 證書進行認證,這意味着 Sentry 服務必須在本地運行或部署到你的託管環境,比如 Kubernetes 集羣。
下面的 nodeappconfig
例子顯示瞭如何拒絕來自 pythonapp
的 neworder
方法的訪問,其中 pythonapp
是在 myDomain
信任域和 default
命名空間中,nodeapp
在 public
公共信任域中。
# nodeappconfig.yaml
apiVersion: dapr.io/v1alpha1
kind: Configuration
metadata:
name: nodeappconfig
spec:
tracing:
samplingRate: "1"
accessControl:
defaultAction: allow
trustDomain: "public"
policies:
- appId: pythonapp
defaultAction: allow
trustDomain: "myDomain"
namespace: "default"
operations:
- name: /neworder
httpVerb: ["POST"]
action: deny
# pythonappconfig.yaml
apiVersion: dapr.io/v1alpha1
kind: Configuration
metadata:
name: pythonappconfig
spec:
tracing:
samplingRate: "1"
accessControl:
defaultAction: allow
trustDomain: "myDomain"
接下來我們先在本地自拓管模式下來使用啓用訪問策略配置,首先需要在啓用 mTLS 的情況下在本地運行 Sentry 服務,我們可以直接在 https://github.com/dapr/dapr/releases 頁面下載對應的 sentry
二進制文件,比如我們這裏是 Mac M1,則可以使用下面的命令直接下載:
# wget https://github.91chi.fun/https://github.com/dapr/dapr/releases/download/v1.8.4/sentry_darwin_arm64.tar.gz
$ wget https://github.com/dapr/dapr/releases/download/v1.8.4/sentry_darwin_arm64.tar.gz
$ tar -xvf sentry_darwin_arm64.tar.gz
然後爲 Sentry 服務創建一個目錄以創建自簽名根證書:
$ mkdir -p $HOME/.dapr/certs
使用以下命令在本地運行 Sentry 服務:
$ ./sentry --issuer-credentials $HOME/.dapr/certs --trust-domain cluster.local
INFO[0000] starting sentry certificate authority -- version 1.8.4 -- commit 18575823c74318c811d6cd6f57ffac76d5debe93 instance=MBP2022.local scope=dapr.sentry type=log ver=1.8.4
INFO[0000] configuration: [port]: 50001, [ca store]: default, [allowed clock skew]: 15m0s, [workload cert ttl]: 24h0m0s instance=MBP2022.local scope=dapr.sentry.config type=log ver=1.8.4
WARN[0000] loading default config. couldn't find config name: daprsystem: stat daprsystem: no such file or directory instance=MBP2022.local scope=dapr.sentry type=log ver=1.8.4
INFO[0000] starting watch on filesystem directory: /Users/cnych/.dapr/certs instance=MBP2022.local scope=dapr.sentry type=log ver=1.8.4
INFO[0000] certificate authority loaded instance=MBP2022.local scope=dapr.sentry type=log ver=1.8.4
INFO[0000] root and issuer certs not found: generating self signed CA instance=MBP2022.local scope=dapr.sentry.ca type=log ver=1.8.4
# ......
INFO[0000] sentry certificate authority is running, protecting ya'll instance=MBP2022.local scope=dapr.sentry type=log ver=1.8.4
運行成功後 Sentry 服務將在指定目錄中創建根證書,可以通過如下所示的命令來配置環境變量指定相關證書路徑:
export DAPR_TRUST_ANCHORS=`cat $HOME/.dapr/certs/ca.crt`
export DAPR_CERT_CHAIN=`cat $HOME/.dapr/certs/issuer.crt`
export DAPR_CERT_KEY=`cat $HOME/.dapr/certs/issuer.key`
export NAMESPACE=default
然後我們就可以運行 daprd
爲啓用了 mTLS 的 node.js 應用啓動 Dapr sidecar,並引用本地的 Sentry 服務:
daprd --app-id nodeapp --dapr-grpc-port 50002 -dapr-http-port 3501 -metrics-port 9091 --log-level debug --app-port 3000 --enable-mtls --sentry-address localhost:50001 --config nodeappconfig.yaml
上面的命令我們通過 --enable-mtls
啓用了 mTLS,通過 --config
指定了上面的 nodeappconfig.yaml
這個配置文件。
然後啓動 node.js 應用:
$ cd node && yarn
$ node app.js
Node App listening on port 3000!
同樣的方式在另外的終端中設置環境變量:
export DAPR_TRUST_ANCHORS=`cat $HOME/.dapr/certs/ca.crt`
export DAPR_CERT_CHAIN=`cat $HOME/.dapr/certs/issuer.crt`
export DAPR_CERT_KEY=`cat $HOME/.dapr/certs/issuer.key`
export NAMESPACE=default
然後運行 daprd
爲啓用了 mTLS 的 python 應用啓動 Dapr sidecar,並引用本地的 Sentry 服務:
daprd --app-id pythonapp --dapr-grpc-port 50003 --metrics-port 9092 --log-level debug --enable-mtls --sentry-address localhost:50001 --config pythonappconfig.yaml
在重新開一個終端直接啓動 Python 應用即可:
$ cd python && pip3 install -r requirements.txt
$ python3 app.py
HTTP 403 => {"errorCode":"ERR_DIRECT_INVOKE","message":"fail to invoke, id: nodeapp, err: rpc error: code = PermissionDenied desc = access control policy has denied access to appid: pythonapp operation: neworder verb: POST"}
HTTP 403 => {"errorCode":"ERR_DIRECT_INVOKE","message":"fail to invoke, id: nodeapp, err: rpc error: code = PermissionDenied desc = access control policy has denied access to appid: pythonapp operation: neworder verb: POST"}
# ......
由於 nodeappconfig
文件中我們配置了對 /neworder
接口的 POST 拒絕操作,所以應該會在 python 應用程序命令提示符中看到對 node.js 應用程序的調用失敗,如果我們將上面的 nodeappconfig
配置中的 action: deny
修改爲 action: allow
並重新運行應用程序,然後我們應該會看到此調用成功。
對於 Kubernetes 模式則更簡單,只需要創建上述配置文件 nodeappconfig.yaml
和 pythonappconfig.yaml
並將其應用於 Kubernetes 集羣,然後在應用的註解中添加 dapr.io/config: "pythonappconfig"
來指定配置即可開啓服務訪問控制。
annotations:
dapr.io/enabled: "true"
dapr.io/app-id: "pythonapp"
dapr.io/config: "pythonappconfig"
Pub/sub 主題範圍訪問策略
對於 Pub/sub
組件,你可以限制允許哪些主題類型和應用程序發佈和訂閱特定主題。
命名空間或組件範圍可用於限制組件對特定應用程序的訪問,這些添加到組件的應用程序範圍僅限制具有特定 ID 的應用程序能夠使用該組件。如下所示顯示瞭如何將兩個啓用 Dapr 的應用程序(應用程序 ID 爲 app1
和 app2
)授予名爲 statestore
的 Redis 組件,該組件本身位於 production
命名空間中:
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: statestore
namespace: production
spec:
type: state.redis
version: v1
metadata:
- name: redisHost
value: redis-master:6379
scopes:
- app1
- app2
除了這個通用組件的 scopes 範圍之外,發佈 / 訂閱組件還可以限制以下內容:
-
可以使用哪些主題(發佈或訂閱)
-
允許哪些應用發佈到特定主題
-
允許哪些應用訂閱特定主題
這被稱爲發佈 / 訂閱主題範圍。我們可以爲每個發佈 / 訂閱組件定義發佈 / 訂閱範圍,比如你可能有一個名爲 pubsub
的 pub/sub 組件,它具有一組範圍,另一個 pubsub2
具有另外不同的範圍。
示例 1:主題訪問範圍。如果你的主題包含敏感信息並且僅允許你的應用程序的子集發佈或訂閱這些信息,那麼限制哪些應用程序可以發佈 / 訂閱主題可能會很有用。如下以下是三個應用程序和三個主題的示例:
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: pubsub
spec:
type: pubsub.redis
version: v1
metadata:
- name: redisHost
value: "localhost:6379"
- name: redisPassword
value: ""
- name: publishingScopes
value: "app1=topic1;app2=topic2,topic3;app3="
- name: subscriptionScopes
value: "app2=;app3=topic1"
這裏我們設置了 publishingScopes
和 subscriptionScopes
兩個屬性,分別用於配置發佈範圍和訂閱範圍。要拒絕應用發佈到任何主題,請將主題列表留空,比如我們這裏配置的 app1=topic1;app2=topic2,topic3;app3=
,其中的 app3=
就表示該應用不允許發佈到任何主題上去。
根據我們的配置下表顯示了允許哪些應用程序發佈到主題中:
下表顯示了哪些應用程序可以訂閱主題:
注意:如果未列出應用程序(例如,
subscriptionScopes
中的app1
),則允許它訂閱所有主題。因爲不使用allowedTopics
並且app1
沒有任何訂閱範圍,所以它也可以使用上面未列出的其他主題。
示例 2:限制允許的主題。如果 Dapr 應用程序向其發送消息,則會創建一個主題,在某些情況下,應管理此主題的創建。例如:
-
Dapr 應用程序中生成主題名稱的錯誤可能導致創建無限數量的主題
-
精簡主題名稱和總數,防止主題無限增長
在這些情況下,可以使用 allowedTopics
屬性進行配置,以下就是三個允許主題的示例:
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: pubsub
spec:
type: pubsub.redis
version: v1
metadata:
- name: redisHost
value: "localhost:6379"
- name: redisPassword
value: ""
- name: allowedTopics
value: "topic1,topic2,topic3"
示例 3:組合 allowedTopics 和範圍。有時你想結合這兩個範圍,因此只有一組固定的允許主題併爲某些應用程序指定範圍。以下是三個應用程序和兩個主題的示例:
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: pubsub
spec:
type: pubsub.redis
version: v1
metadata:
- name: redisHost
value: "localhost:6379"
- name: redisPassword
value: ""
- name: allowedTopics
value: "A,B"
- name: publishingScopes
value: "app1=A"
- name: subscriptionScopes
value: "app1=;app2=A"
注意這裏我們沒有列出第三個應用程序,如果沒有在範圍內指定應用程序,則允許它使用所有主題。
根據上面的配置下表顯示了允許哪個應用程序發佈到主題中:
下表顯示了允許哪個應用程序訂閱主題:
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/k6ImEWGd2nM8fxgfDBfP-Q