安全是 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 = public、namespace = default、appId = app1,使用如下所示的配置,允许所有 appId = app1 的调用方法,并拒绝来自其他应用程序的所有其他调用请求。
复制
apiVersion: dapr.io/v1alpha1kind: Configurationmetadata: name: appconfigspec: accessControl: defaultAction: denytrustDomain: "public"policies: -appId: app1defaultAction: allowtrustDomain: "public"namespace: "default"
场景 2:拒绝访问除 trustDomain = public、namespace = default、appId = app1、operation = op1 之外的所有应用程序,使用此配置仅允许来自 appId = app1 的方法 op1,并且拒绝来自所有其他应用程序的所有其他方法请求,包括 app1 上的其他方法。
复制
apiVersion: dapr.io/v1alpha1kind: Configurationmetadata: name: appconfigspec: accessControl: defaultAction: denytrustDomain: "public"policies: -appId: app1defaultAction: denytrustDomain: "public"namespace: "default"operations: -name: /op1httpVerb: ["*"] action: allow
场景 3:拒绝对所有应用程序的访问,除非 HTTP 的特定 verb 和 GRPC 的操作匹配,使用如下所示的配置,仅允许以下场景访问,并且来自所有其他应用程序的所有其他方法请求(包括 app1 或 app2 上的其他方法)都会被拒绝。
复制
apiVersion: dapr.io/v1alpha1kind: Configurationmetadata: name: appconfigspec: accessControl: defaultAction: denytrustDomain: "public"policies: -appId: app1defaultAction: denytrustDomain: "public"namespace: "default"operations: -name: /op1httpVerb: ["POST", "PUT"] action: allow-appId: app2defaultAction: denytrustDomain: "myDomain"namespace: "ns1"operations: -name: /op2action: allow
场景 4:允许访问除 trustDomain = public、namespace = default、appId = app1、operation = /op1/* 所有 http verb 之外的所有方法。
复制
apiVersion: dapr.io/v1alpha1kind: Configurationmetadata: name: appconfigspec: accessControl: defaultAction: allowtrustDomain: "public"policies: -appId: app1defaultAction: allowtrustDomain: "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/v1alpha1kind: Configurationmetadata: name: appconfigspec: accessControl: defaultAction: allowtrustDomain: "public"policies: -appId: app1defaultAction: allowtrustDomain: "public"namespace: "ns1"-appId: app1defaultAction: denytrustDomain: "public"namespace: "ns2"
场景 6:允许访问除 trustDomain = public、namespace = default、appId = app1、operation = /op1/**/a、所有 http 动词之外的所有方法。
复制
apiVersion: dapr.io/v1alpha1kind: Configurationmetadata: name: appconfigspec: accessControl: defaultAction: allowtrustDomain: "public"policies: -appId: app1defaultAction: allowtrustDomain: "public"namespace: "default"operations: -name: /op1/**/ahttpVerb: ["*"] action: deny
下面我们通过一个具体的示例来展示下访问控制策略的使用,同样还是使用 quickstarts 示例中的 hello-world 进行说明。
复制
gitclone [-b<dapr_version_tag>] https://github.com/dapr/quickstarts.gitcdquickstarts/tutorials/hello-world/node
hello world
该示例应用中包含一个 python 应用去调用一个 node.js 应用程序,访问控制列表依靠 Dapr Sentry 服务来生成带有 SPIFFE id 的 TLS 证书进行认证,这意味着 Sentry 服务必须在本地运行或部署到你的托管环境,比如 Kubernetes 集群。
下面的 nodeappconfig 例子显示了如何拒绝来自 pythonapp 的 neworder 方法的访问,其中 pythonapp 是在 myDomain 信任域和 default 命名空间中,nodeapp 在 public 公共信任域中。
复制
apiVersion: dapr.io/v1alpha1kind: Configurationmetadata: name: nodeappconfigspec: tracing: samplingRate: "1"accessControl: defaultAction: allowtrustDomain: "public"policies: -appId: pythonappdefaultAction: allowtrustDomain: "myDomain"namespace: "default"operations: -name: /neworderhttpVerb: ["POST"] action: deny
复制
apiVersion: dapr.io/v1alpha1kind: Configurationmetadata: name: pythonappconfigspec: tracing: samplingRate: "1"accessControl: defaultAction: allowtrustDomain: "myDomain"
接下来我们先在本地自拓管模式下来使用启用访问策略配置,首先需要在启用 mTLS 的情况下在本地运行 Sentry 服务,我们可以直接在 https://github.com/dapr/dapr/releases 页面下载对应的 sentry 二进制文件,比如我们这里是 Mac M1,则可以使用下面的命令直接下载:
复制
$wgethttps://github.com/dapr/dapr/releases/download/v1.8.4/sentry_darwin_arm64.tar.gz$tar-xvfsentry_darwin_arm64.tar.gz
然后为 Sentry 服务创建一个目录以创建自签名根证书:
复制
$mkdir-p$HOME/.dapr/certs
使用以下命令在本地运行 Sentry 服务:
复制
$ ./sentry--issuer-credentials$HOME/.dapr/certs--trust-domaincluster.localINFO[0000] startingsentrycertificateauthority--version1.8.4--commit18575823c74318c811d6cd6f57ffac76d5debe93instance=MBP2022.localscope=dapr.sentrytype=logver=1.8.4INFO[0000] configuration: [port]: 50001, [castore]: default, [allowedclockskew]: 15m0s, [workloadcertttl]: 24h0m0sinstance=MBP2022.localscope=dapr.sentry.configtype=logver=1.8.4WARN[0000] loadingdefaultconfig. couldn't find config name: daprsystem: stat daprsystem: no such file or directory instance=MBP2022.local scope=dapr.sentry type=log ver=1.8.4INFO[0000] startingwatchonfilesystemdirectory: /Users/cnych/.dapr/certsinstance=MBP2022.localscope=dapr.sentrytype=logver=1.8.4INFO[0000] certificateauthorityloadedinstance=MBP2022.localscope=dapr.sentrytype=logver=1.8.4INFO[0000] rootandissuercertsnotfound: generatingselfsignedCAinstance=MBP2022.localscope=dapr.sentry.catype=logver=1.8.4# ......INFO[0000] sentrycertificateauthorityisrunning, protectingya'll instance=MBP2022.local scope=dapr.sentry type=log ver=1.8.4
运行成功后 Sentry 服务将在指定目录中创建根证书,可以通过如下所示的命令来配置环境变量指定相关证书路径:
复制
exportDAPR_TRUST_ANCHORS=`cat$HOME/.dapr/certs/ca.crt`exportDAPR_CERT_CHAIN=`cat$HOME/.dapr/certs/issuer.crt`exportDAPR_CERT_KEY=`cat$HOME/.dapr/certs/issuer.key`exportNAMESPACE=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 应用:
复制
$cdnode&&yarn$nodeapp.jsNodeApplisteningonport3000!
同样的方式在另外的终端中设置环境变量:
复制
exportDAPR_TRUST_ANCHORS=`cat$HOME/.dapr/certs/ca.crt`exportDAPR_CERT_CHAIN=`cat$HOME/.dapr/certs/issuer.crt`exportDAPR_CERT_KEY=`cat$HOME/.dapr/certs/issuer.key`exportNAMESPACE=default
然后运行 daprd 为启用了 mTLS 的 python 应用启动 Dapr sidecar,并引用本地的 Sentry 服务:
复制
daprd--app-idpythonapp--dapr-grpc-port50003--metrics-port9092--log-leveldebug--enable-mtls--sentry-addresslocalhost:50001--configpythonappconfig.yaml
在重新开一个终端直接启动 Python 应用即可:
复制
$cdpython&&pip3install-rrequirements.txt$python3app.pyHTTP403=> {"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"}HTTP403=> {"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/v1alpha1kind: Componentmetadata: name: statestorenamespace: productionspec: type: state.redisversion: v1metadata: -name: redisHostvalue: redis-master:6379scopes: -app1-app2
除了这个通用组件的 scopes 范围之外,发布/订阅组件还可以限制以下内容:
这被称为发布/订阅主题范围。我们可以为每个发布/订阅组件定义发布/订阅范围,比如你可能有一个名为 pubsub 的 pub/sub 组件,它具有一组范围,另一个 pubsub2 具有另外不同的范围。
示例 1:主题访问范围。如果你的主题包含敏感信息并且仅允许你的应用程序的子集发布或订阅这些信息,那么限制哪些应用程序可以发布/订阅主题可能会很有用。如下以下是三个应用程序和三个主题的示例:
复制
apiVersion: dapr.io/v1alpha1kind: Componentmetadata: name: pubsubspec: type: pubsub.redisversion: v1metadata: -name: redisHostvalue: "localhost:6379"-name: redisPasswordvalue: ""-name: publishingScopesvalue: "app1=topic1;app2=topic2,topic3;app3="-name: subscriptionScopesvalue: "app2=;app3=topic1"
这里我们设置了 publishingScopes 和 subscriptionScopes 两个属性,分别用于配置发布范围和订阅范围。要拒绝应用发布到任何主题,请将主题列表留空,比如我们这里配置的 app1=topic1;app2=topic2,topic3;app3=,其中的 app3= 就表示该应用不允许发布到任何主题上去。
根据我们的配置下表显示了允许哪些应用程序发布到主题中:
下表显示了哪些应用程序可以订阅主题:
注意:如果未列出应用程序(例如,subscriptionScopes 中的 app1),则允许它订阅所有主题。因为不使用 allowedTopics 并且 app1 没有任何订阅范围,所以它也可以使用上面未列出的其他主题。
示例 2:限制允许的主题。如果 Dapr 应用程序向其发送消息,则会创建一个主题,在某些情况下,应管理此主题的创建。例如:
在这些情况下,可以使用 allowedTopics 属性进行配置,以下就是三个允许主题的示例:
复制
apiVersion: dapr.io/v1alpha1kind: Componentmetadata: name: pubsubspec: type: pubsub.redisversion: v1metadata: -name: redisHostvalue: "localhost:6379"-name: redisPasswordvalue: ""-name: allowedTopicsvalue: "topic1,topic2,topic3"
示例 3:组合 allowedTopics 和范围。有时你想结合这两个范围,因此只有一组固定的允许主题并为某些应用程序指定范围。以下是三个应用程序和两个主题的示例:
复制
apiVersion: dapr.io/v1alpha1kind: Componentmetadata: name: pubsubspec: type: pubsub.redisversion: v1metadata: -name: redisHostvalue: "localhost:6379"-name: redisPasswordvalue: ""-name: allowedTopicsvalue: "A,B"-name: publishingScopesvalue: "app1=A"-name: subscriptionScopesvalue: "app1=;app2=A"
注意这里我们没有列出第三个应用程序,如果没有在范围内指定应用程序,则允许它使用所有主题。
根据上面的配置下表显示了允许哪个应用程序发布到主题中:
下表显示了允许哪个应用程序订阅主题: