使用 Kyverno 基于策略的鉴权

利用基于 CEL 的策略,使用 Kyverno 的 Authz 服务器委托七层鉴权决策逻辑。

Nov 25, 2024 | 作者 Charles-Edouard Brétéché - Nirmata; Translated by Wilson Wu - DaoCloud

Istio 支持与许多不同项目的集成。Istio 博客最近发表了一篇关于使用 OpenPolicyAgent 实现 L7 策略功能的文章。 Kyverno 是一个类似的项目,今天我们将深入探讨如何将 Istio 和 Kyverno Authz 服务器结合使用, 以在您的平台中实施七层策略。

我们将通过一个简单的示例向您展示如何开始。您将看到这种组合如何成为一种可靠的选择, 可以快速透明地向企业中任何地方的应用程序团队提供策略,同时还提供安全团队进行审计和合规所需的数据。

尝试一下

与 Istio 集成时,Kyverno Authz 服务器可用于为微服务实施细粒度的访问控制策略。

本指南介绍如何为简单的微服务应用程序实施访问控制策略。

先决条件

安装 Istio 并配置你的网格选项以启用 Kyverno:

$ istioctl install -y -f - <<EOF
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
spec:
  meshConfig:
    accessLogFile: /dev/stdout
    accessLogFormat: |
      [KYVERNO DEMO] my-new-dynamic-metadata: '%DYNAMIC_METADATA(envoy.filters.http.ext_authz)%'
    extensionProviders:
    - name: kyverno-authz-server
      envoyExtAuthzGrpc:
        service: kyverno-authz-server.kyverno.svc.cluster.local
        port: '9081'
EOF

请注意,在配置中,我们定义了一个指向 Kyverno Authz 服务器安装的 extensionProviders 部分:

[...]
    extensionProviders:
    - name: kyverno-authz-server
      envoyExtAuthzGrpc:
        service: kyverno-authz-server.kyverno.svc.cluster.local
        port: '9081'
[...]

部署 Kyverno Authz 服务器

Kyverno Authz 服务器是一个能够处理 Envoy 外部授权请求的 GRPC 服务器。

它可以使用 Kyverno AuthorizationPolicy 资源进行配置,可以存储在集群内或通过外部提供。

$ kubectl create ns kyverno
$ kubectl label namespace kyverno istio-injection=enabled
$ helm install kyverno-authz-server --namespace kyverno --wait --version 0.1.0 --repo https://kyverno.github.io/kyverno-envoy-plugin kyverno-authz-server

部署示例应用程序

httpbin 是一个著名的应用程序,可用于测试 HTTP 请求,并有助于快速展示如何使用请求和响应属性。

$ kubectl create ns my-app
$ kubectl label namespace my-app istio-injection=enabled
$ kubectl apply -f https://raw.githubusercontent.com/istio/istio/master/samples/httpbin/httpbin.yaml -n my-app

部署一个 Istio AuthorizationPolicy

AuthorizationPolicy 定义了将受 Kyverno Authz 服务器保护的服务。

$ kubectl apply -f - <<EOF
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
  name: my-kyverno-authz
  namespace: istio-system # 这将在所有网格上强制执行该策略,istio-system 是网格根命名空间
spec:
  selector:
    matchLabels:
      ext-authz: enabled
  action: CUSTOM
  provider:
    name: kyverno-authz-server
  rules: [{}] # 空规则,它将应用于带有 ext-authz: enabled 标签的选择器
EOF

请注意,在此资源中,我们定义了您在 Istio 配置中设置的 Kyverno Authz 服务器 extensionProvider

[...]
  provider:
    name: kyverno-authz-server
[...]

标记应用程序以执行策略

让我们标记应用程序以执行该策略。Istio AuthorizationPolicy 需要该标签才能应用于示例应用程序 Pod。

$ kubectl patch deploy httpbin -n my-app --type=merge -p='{
  "spec": {
    "template": {
      "metadata": {
        "labels": {
          "ext-authz": "enabled"
        }
      }
    }
  }
}'

部署一个 Kyverno AuthorizationPolicy

Kyverno AuthorizationPolicy 定义了 Kyverno Authz 服务器根据给定的 Envoy CheckRequest 做出决策所使用的规则。

它使用 CEL 语言来分析传入的 CheckRequest, 并预期生成一个 CheckResponse作为返回。

传入的请求在 object 字段下可用,并且策略可以定义可供所有 authorizations 使用的 variables

$ kubectl apply -f - <<EOF
apiVersion: envoy.kyverno.io/v1alpha1
kind: AuthorizationPolicy
metadata:
  name: demo-policy.example.com
spec:
  failurePolicy: Fail
  variables:
  - name: force_authorized
    expression: object.attributes.request.http.?headers["x-force-authorized"].orValue("")
  - name: allowed
    expression: variables.force_authorized in ["enabled", "true"]
  authorizations:
  - expression: >
      variables.allowed
        ? envoy.Allowed().Response()
        : envoy.Denied(403).Response()
EOF

请注意,您可以手动构建 CheckResponse 或使用 CEL 辅助函数(如 envoy.Allowed()envoy.Denied(403))来简化创建响应消息:

[...]
  - expression: >
      variables.allowed
        ? envoy.Allowed().Response()
        : envoy.Denied(403).Response()
[...]

工作原理

当应用 AuthorizationPolicy 时,Istio 控制平面(istiod)将所需的配置发送到策略中选定服务的 Sidecar 代理(Envoy)。 然后,Envoy 会将请求发送到 Kyverno Authz 服务器以检查该请求是否被允许。

Istio 和 Kyverno Authz 服务器

Envoy 代理通过配置链中的过滤器来工作。其中一个过滤器是 ext_authz, 它使用特定消息实现外部授权服务。任何实现正确 protobuf 的服务器都可以连接到 Envoy 代理并提供授权决策;Kyverno Authz 服务器就是其中之一。

筛选器

查看 Envoy 的授权服务文档, 可以看到该消息具有以下属性:

这意味着根据来自 Authz 服务器的响应,Envoy 可以添加或删除标头、查询参数,甚至更改响应体。

我们也可以这样做,如 Kyverno Authz 服务器文档中所述。

测试

让我们测试简单的用法(鉴权),然后创建一个更高级的策略来展示如何使用 Kyverno Authz 服务器来修改请求和响应。

部署一个应用程序来对 httpbin 示例应用程序运行 curl 命令:

$ kubectl apply -n my-app -f https://raw.githubusercontent.com/istio/istio/master/samples/curl/curl.yaml

应用策略:

$ kubectl apply -f - <<EOF
apiVersion: envoy.kyverno.io/v1alpha1
kind: AuthorizationPolicy
metadata:
  name: demo-policy.example.com
spec:
  failurePolicy: Fail
  variables:
  - name: force_authorized
    expression: object.attributes.request.http.?headers["x-force-authorized"].orValue("")
  - name: allowed
    expression: variables.force_authorized in ["enabled", "true"]
  authorizations:
  - expression: >
      variables.allowed
        ? envoy.Allowed().Response()
        : envoy.Denied(403).Response()
EOF

简单的场景是,如果请求包含标头 x-force-authorized, 且值为 enabledtrue,则允许请求。如果标头不存在或具有不同的值,则请求将被拒绝。

在这种情况下,我们将允许和拒绝响应处理组合在一个表达式中。 但是也可以使用多个表达式,第一个返回非空响应的表达式将由 Kyverno Authz 服务器使用, 当规则不想做出决定并委托给下一个规则时,这很有用:

[...]
  authorizations:
  # 当标头值匹配时允许请求
  - expression: >
      variables.allowed
        ? envoy.Allowed().Response()
        : null
  # 否则拒绝请求
  - expression: >
      envoy.Denied(403).Response()
[...]

简单规则

以下请求将返回 403

$ kubectl exec -n my-app deploy/curl -- curl -s -w "\nhttp_code=%{http_code}" httpbin:8000/get

以下请求将返回 200

$ kubectl exec -n my-app deploy/curl -- curl -s -w "\nhttp_code=%{http_code}" httpbin:8000/get -H "x-force-authorized: true"

高级操作

现在更高级的用例,应用第二条策略:

$ kubectl apply -f - <<EOF
apiVersion: envoy.kyverno.io/v1alpha1
kind: AuthorizationPolicy
metadata:
  name: demo-policy.example.com
spec:
  variables:
  - name: force_authorized
    expression: object.attributes.request.http.headers[?"x-force-authorized"].orValue("") in ["enabled", "true"]
  - name: force_unauthenticated
    expression: object.attributes.request.http.headers[?"x-force-unauthenticated"].orValue("") in ["enabled", "true"]
  - name: metadata
    expression: '{"my-new-metadata": "my-new-value"}'
  authorizations:
    # 如果 force_unauthenticated -> 401
  - expression: >
      variables.force_unauthenticated
        ? envoy
            .Denied(401)
            .WithBody("Authentication Failed")
            .Response()
        : null
    # 如果 force_authorized -> 200
  - expression: >
      variables.force_authorized
        ? envoy
            .Allowed()
            .WithHeader("x-validated-by", "my-security-checkpoint")
            .WithoutHeader("x-force-authorized")
            .WithResponseHeader("x-add-custom-response-header", "added")
            .Response()
            .WithMetadata(variables.metadata)
        : null
    # 否则 -> 403
  - expression: >
      envoy
        .Denied(403)
        .WithBody("Unauthorized Request")
        .Response()
EOF

在该政策中,您可以看到:

相应的 CheckResponse 将从 Kyverno Authz 服务器返回到 Envoy 代理。 Envoy 将使用这些值来相应地修改请求和响应。

更改返回体

让我们测试一下新功能:

$ kubectl exec -n my-app deploy/curl -- curl -s -w "\nhttp_code=%{http_code}" httpbin:8000/get

现在我们可以改变响应体。

使用 403 时主体将更改为 “Unauthorized Request”,运行前面的命令,您应该收到:

Unauthorized Request
http_code=403

更改返回体和状态码

运行带有标头 x-force-unauthenticated: true 的请求:

$ kubectl exec -n my-app deploy/curl -- curl -s -w "\nhttp_code=%{http_code}" httpbin:8000/get -H "x-force-unauthenticated: true"

这次您应该收到 “Authentication Failed” 响应体和错误 401

Authentication Failed
http_code=401

向请求添加标头

运行有效请求:

$ kubectl exec -n my-app deploy/curl -- curl -s -w "\nhttp_code=%{http_code}" httpbin:8000/get -H "x-force-authorized: true"

您应该收到带有新标头 x-validated-by: my-security-checkpoint 且标头 x-force-authorized 被删除的回显体:

[...]
    "X-Validated-By": [
      "my-security-checkpoint"
    ]
[...]
http_code=200

向响应添加标头

运行相同的请求但仅显示标头:

$ kubectl exec -n my-app deploy/curl -- curl -s -I -w "\nhttp_code=%{http_code}" httpbin:8000/get -H "x-force-authorized: true"

你会发现在 Authz 检查期间添加的响应标头 x-add-custom-response-header:added

HTTP/1.1 200 OK
[...]
x-add-custom-response-header: added
[...]
http_code=200

过滤器之间共享数据

最后,您可以使用 dynamic_metadata 将数据传递给以下 Envoy 过滤器。

当您想要将数据传递给链中的另一个 ext_authz 过滤器或想要将其打印在应用程序日志中时,这很有用。

元数据

为此,请检查您之前设置的访问日志格式:

[...]
    accessLogFormat: |
      [KYVERNO DEMO] my-new-dynamic-metadata: "%DYNAMIC_METADATA(envoy.filters.http.ext_authz)%"
[...]

DYNAMIC_METADATA 是访问元数据对象的保留关键字。其余部分是要访问的过滤器的名称。

在我们的例子中,名称 envoy.filters.http.ext_authz 由 Istio 自动创建。 您可以通过转储 Envoy 配置来验证这一点:

$ istioctl pc all deploy/httpbin -n my-app -oyaml | grep envoy.filters.http.ext_authz

您将看到过滤器的配置。

让我们测试一下动态元数据。在高级规则中,我们正在创建一个新的元数据条目: {"my-new-metadata": "my-new-value"}

运行请求并检查应用程序的日志:

$ kubectl exec -n my-app deploy/curl -- curl -s -I httpbin:8000/get -H "x-force-authorized: true"
$ kubectl logs -n my-app deploy/httpbin -c istio-proxy --tail 1

您将在输出中看到 Kyverno 策略配置的新属性:

[...]
[KYVERNO DEMO] my-new-dynamic-metadata: '{"my-new-metadata":"my-new-value","ext_authz_duration":5}'
[...]

结论

在本指南中,我们展示了如何集成 Istio 和 Kyverno Authz 服务器来为简单的微服务应用程序实施策略。 我们还展示了如何使用策略来修改请求和响应属性。

这是构建可供所有应用程序团队使用的平台范围策略系统的基础示例。

Share this post