Dapr 安全性之訪問控制策略

安全是 Dapr 的基礎,本文我們將來說明在分佈式應用中使用 Dapr 時的安全特性和能力,主要可以分爲以下幾個方面。

Dapr 通過服務調用 API 提供端到端的安全性,能夠使用 Dapr 對應用程序進行身份驗證並設置端點訪問策略。

服務調用範圍訪問策略

跨命名空間的服務調用

Dapr 應用程序可以被限定在特定的命名空間,以實現部署和安全,當然我們仍然可以在部署到不同命名空間的服務之間進行調用。默認情況下,服務調用支持通過簡單地引用應用 ID (比如 nodeapp) 來調用同一命名空間內的服務:

localhost:3500/v1.0/invoke/nodeapp/method/neworder

服務調用還支持跨命名空間的調用,在所有受支持的託管平臺上,Dapr 應用程序 ID 符合包含目標命名空間的有效 FQDN 格式,可以同時指定:

比如在 production 命名空間中的 nodeapp 應用上調用 neworder 方法,則可以使用下面的方式:

localhost:3500/v1.0/invoke/nodeapp.production/method/neworder

當使用服務調用在命名空間中調用應用程序時,我們可以使用命名空間對其進行限定,特別在 Kubernetes 集羣中的跨命名空間調用是非常有用的。

爲服務調用應用訪問控制列表配置

訪問控制策略在配置文件中被指定,並被應用於被調用應用程序的 Dapr sidecar,對被調用應用程序的訪問是基於匹配的策略動作,你可以爲所有調用應用程序提供一個默認的全局動作,如果沒有指定訪問控制策略,默認行爲是允許所有調用應用程序訪問被調用的應用程序。

在具體學習訪問控制策略配置之前,我們需要先了解兩個概念:

訪問控制策略會遵循如下所示的一些規則:

下面是一些使用訪問控制列表進行服務調用的示例場景。

場景 1:拒絕所有應用程序的訪問,除非 trustDomain = publicnamespace = defaultappId = 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 = publicnamespace = defaultappId = app1operation = 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 的操作匹配,使用如下所示的配置,僅允許以下場景訪問,並且來自所有其他應用程序的所有其他方法請求(包括 app1app2 上的其他方法)都會被拒絕。

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 = publicnamespace = defaultappId = app1operation = /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 = publicnamespace = ns1appId = app1 的所有方法並拒絕訪問 trustDomain = publicnamespace = ns2appId = 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 = publicnamespace = defaultappId = app1operation = /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 例子顯示瞭如何拒絕來自 pythonappneworder 方法的訪問,其中 pythonapp 是在 myDomain 信任域和 default 命名空間中,nodeapppublic 公共信任域中。

# 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.yamlpythonappconfig.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 爲 app1app2)授予名爲 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"

這裏我們設置了 publishingScopessubscriptionScopes 兩個屬性,分別用於配置發佈範圍和訂閱範圍。要拒絕應用發佈到任何主題,請將主題列表留空,比如我們這裏配置的 app1=topic1;app2=topic2,topic3;app3=,其中的 app3= 就表示該應用不允許發佈到任何主題上去。

根據我們的配置下表顯示了允許哪些應用程序發佈到主題中:

ehz4nf

下表顯示了哪些應用程序可以訂閱主題:

xiJPBK

注意:如果未列出應用程序(例如,subscriptionScopes 中的 app1),則允許它訂閱所有主題。因爲不使用 allowedTopics 並且 app1 沒有任何訂閱範圍,所以它也可以使用上面未列出的其他主題。

示例 2:限制允許的主題。如果 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"

注意這裏我們沒有列出第三個應用程序,如果沒有在範圍內指定應用程序,則允許它使用所有主題。

根據上面的配置下表顯示了允許哪個應用程序發佈到主題中:

QCbp39

下表顯示了允許哪個應用程序訂閱主題:

TxoURW

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