[译] 使用 Istio 和 Kyverno Authz Server 实现动态 Layer 7 策略管理

使用 Kyverno 的 Authz Server 代理 Layer 7 授权决策逻辑,并基于 CEL 编写策略。

声明
此文为个人翻译,仅供参考,不代表我个人立场。翻译过程中可能有删改或遗漏,如需了解原文,请自行查阅。如有疏漏,欢迎指正。
查看本文大纲

Istio 支持与多个不同的项目集成。Istio 博客最近发布了一篇关于 与 OpenPolicyAgent 的 L7 策略功能 的文章。Kyverno 是一个类似的项目,本文将探讨如何将 Istio 和 Kyverno Authz Server 结合使用,在你的平台中强制执行 Layer 7 策略。

我们将通过一个简单的示例,展示如何快速上手。你将看到这种组合如何成为快速、透明地将策略交付给企业中各个应用团队的可靠选择,同时提供安全团队所需的审计和合规性数据。

实验指南

将 Kyverno Authz Server 集成到 Istio 中后,可以针对微服务应用程序强制执行细粒度的访问控制策略。

本指南展示了如何为一个简单的微服务应用程序实施访问控制策略。

前置条件

  • 安装了 Istio 的 Kubernetes 集群。
  • 已安装 istioctl 命令行工具。

首先安装 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 Server 的 extensionProviders 部分:

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

部署 Kyverno Authz Server

Kyverno Authz Server 是一个支持处理 Envoy 外部授权请求的 GRPC 服务。它可以通过集群内存储的 Kyverno AuthorizationPolicy 资源或外部提供的资源进行配置。

kubectl create ns kyverno
kubectl label namespace kyverno istio-injection=enabled
helm install kyverno-authz-server --namespace kyverno --wait --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/release-1.24/samples/httpbin/httpbin.yaml -n my-app

部署 Istio AuthorizationPolicy

AuthorizationPolicy 定义了将由 Kyverno Authz Server 保护的服务。

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 Server extensionProvider

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

给应用打标签以强制执行策略

为应用打标签,以便 Istio 的 AuthorizationPolicy 对示例应用的 Pods 生效:

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

部署 Kyverno AuthorizationPolicy

Kyverno 的 AuthorizationPolicy 定义了 Kyverno Authz Server 根据给定的 Envoy CheckRequest 做出决策的规则。

策略使用 CEL 语言 分析传入的 CheckRequest,并生成相应的 CheckResponse

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

在此配置中,你可以使用 CEL 辅助函数如 envoy.Allowed()envoy.Denied(403) 来简化响应消息的创建:

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

工作原理

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

image
工作原理

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

image
工作原理

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

  • Ok 响应:

    {
      "status": {...},
      "ok_response": {
        "headers": [],
        "headers_to_remove": [],
        "response_headers_to_add": [],
        "query_parameters_to_set": [],
        "query_parameters_to_remove": []
      },
      "dynamic_metadata": {...}
    }
    
  • 拒绝的响应:

    {
      "status": {...},
      "denied_response": {
        "status": {...},
        "headers": [],
        "body": "..."
      },
      "dynamic_metadata": {...}
    }
    

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

我们也可以这样做,如 Kyverno 授权服务器文档中所记载的那样。

测试

我们将先测试简单的授权场景,然后创建一个更高级的策略,展示如何使用 Kyverno Authz Server 修改请求和响应。

部署一个测试应用

部署一个可以向 httpbin 示例应用运行 curl 命令的应用程序:

kubectl apply -n my-app -f https://raw.githubusercontent.com/istio/istio/release-1.24/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 Server 使用,这种方法在某些规则不想做决策并将决策权委托给下一个规则时非常有用:

[...]
  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

在该策略中,逻辑如下:

  • 如果请求包含标头 x-force-unauthenticated: truex-force-unauthenticated: enabled,我们将返回 401,并包含响应正文 “Authentication Failed”。
  • 否则,如果请求包含标头 x-force-authorized: truex-force-authorized: enabled,我们将返回 200,并操作请求标头、响应标头并注入动态元数据。
  • 在所有其他情况下,我们将返回 403,并包含响应正文 “Unauthorized Request”。

Kyverno Authz Server 将返回相应的 CheckResponse 给 Envoy 代理。Envoy 将使用这些值相应地修改请求和响应。

更改返回的响应正文

让我们测试新功能。以下命令返回 403 并将响应正文改为 “Unauthorized Request”:

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

输出如下:

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 过滤器或希望在应用日志中打印数据时非常有用。

image
元数据

要实现这一点,请回顾之前设置的访问日志格式:

[...]
    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 Server 集成以在简单的微服务应用程序中强制执行策略。我们还展示了如何使用策略修改请求和响应的属性。

这是构建企业级策略系统的基础示例,适用于所有应用团队。通过这一系统,策略可以快速部署,并为安全团队提供所需的审计和合规数据。

如需深入了解,请参考 Kyverno Authz Server 文档。如果你希望更详细地了解实现过程或示例用例,可以随时联系我们!

最后更新于 2024/12/03