更好的外部授权

背景

istio 中的授权策略为网格内部的服务提供访问控制。授权策略是快速、强大及被广泛使用的功能,自istio 1.4首次发布以来,我们进行了持续改进,以使策略更加灵活,包含 DENY action, 排除语义, X-Forwarded-For 头支持, 嵌套 JWT claim 支持等,这些功能提高了授权策略的灵活性,但是此模型仍然不支持许多用例,例如:

  • 您拥有自己的内部授权系统,该系统无法轻松迁移到授权策略或无法轻松地被其替换。

  • 您想与第三方解决方案(例如,opa 或oauth2代理)集成,该解决方案可能需要使用Istio中的底层Envoy配置API,或者根本无法使用。

  • 对于您的用例,授权策略缺乏必要的语义。

解决方案

在istio 1.9中,引入了 CUSTOM action来实现对授权策略的可扩展性,该操作使您可以将访问控制决策委派给外部授权服务。

CUSTOM action使您可以将Istio与实现其自己的自定义授权逻辑的外部授权系统集成。下图显示了此集成的高级体系结构:

在配置时,网格管理员使用一种CUSTOM action来配置授权策略,以在代理(网关或Sidecar)上启用外部授权。管理员应验证外部身份验证服务已启动并正在运行。

在运行时,

  1. 代理将拦截请求,代理将按照用户在授权策略中配置的方式将检查请求发送到外部身份验证服务。

  2. 外部身份验证服务将决定是否允许它。

  3. 如果允许,该请求将继续,并将由ALLOW/ DENYaction定义的任何本地授权强制执行。

  4. 如果被拒绝,该请求将立即被拒绝。

让我们看一下带有该CUSTOM动作的示例授权策略:

apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: ext-authz
  namespace: istio-system
spec:
  # 选择器适用于istio-system 命名空间中的入口网关。
  selector:
    matchLabels:
      app: istio-ingressgateway
  # CUSTOM action将访问控制委派给外部授权者,这与在代理内部强制执行访问控制的ALLOW / DENY操作不同。
  action: CUSTOM
  # 提供程序指定在meshconfig中定义的外部授权者的名称,该名称指示在何处以及如何与外部身份验证服务进行通信。 
  provider:
    name: "my-ext-authz-service"
  # 该规则指定仅当请求路径具有前缀"/admin/"时才触发访问控制。 这使您可以根据请求轻松启用或禁用外部授权,从而避免了不需要外部检查请求的情况。
  rules:
  - to:
    - operation:
        paths: ["/admin/*"]

引用了定义在mesh config中名为my-ext-authz-service 的提供者

extensionProviders:
# 授权策略在其提供者字段中引用名称"my-ext-authz-service"。
- name: "my-ext-authz-service"
  # "envoyExtAuthzGrpc"字段指定由Envoy ext-authz过滤器gRPC API实现的外部授权服务的类型。 另一个受支持的类型是Envoy ext-authz filter HTTP API。
  envoyExtAuthzGrpc:
    # 服务和端口指定外部身份验证服务的地址,"ext-authz.istio-system.svc.cluster.local"表示该服务已部署在网格中。 也可以将其定义为网格之外,甚至可以将其定义为单独的容器。
    service: "ext-authz.istio-system.svc.cluster.local"
    port: 9000

CUSTOM action 授权策略使运行时启用外部授权,它可以被配置为根据使用您已经使用其他action同样的规则要求的外部授权进行触发。

外部授权服务当前在meshconfigAPI中定义,并通过其名称引用。它可以在有或没有代理的情况下部署在网格中。如果使用代理,则可以进一步用于PeerAuthentication在代理和外部授权服务之间启用mTLS。

该CUSTOM action目前处于实验阶段; API可能会根据用户反馈以非向后兼容的方式进行更改。该规则当前不支持与身份验证相关的字段(例如,source principal 或 JWT claim),并且给定工作负载仅允许一个提供程序,但是您仍可以在不同的工作负载上使用不同的提供程序。

OPA示例

在本节中,我们将演示如何将CUSTOM action与opa一起用作入口网关上的外部授权者。我们将有条件地在除/ip之外的所有路径上启用外部授权。

您也可以参考外部授权任务以获取使用示例ext-authz服务器的更基本的介绍

创建示例OPA 策略

运行以下命令,创建一个OPA策略,如果路径的前缀与JWT令牌中的声明"path"(base64编码)匹配,则允许该请求:

cat > policy.rego <<EOF
package envoy.authz

import input.attributes.request.http as http_request

default allow = false

token = {"valid": valid, "payload": payload} {
    [_, encoded] := split(http_request.headers.authorization, " ")
    [valid, _, payload] := io.jwt.decode_verify(encoded, {"secret": "secret"})
}

allow {
    is_token_valid
    action_allowed
}

is_token_valid {
  token.valid
  now := time.now_ns() / 1000000000
  token.payload.nbf <= now
  now < token.payload.exp
}

action_allowed {
  startswith(http_request.path, base64url.decode(token.payload.path))
}
EOF
kubectl create secret generic opa-policy --from-file policy.rego

部署httpbin和OPA

启用sidecar注入

kubectl label ns default istio-injection=enabled

运行以下命令以部署示例应用程序httpbin和OPA。OPA可以作为单独的容器部署在httpbin容器中,也可以完全部署在单独的容器中:

kubectl apply -f - <<EOF
apiVersion: v1
kind: Service
metadata:
  name: opa
  labels:
    app: opa
spec:
  ports:
  - name: grpc
    port: 9191
    targetPort: 9191
  selector:
    app: opa
---
kind: Deployment
apiVersion: apps/v1
metadata:
  name: opa
  labels:
    app: opa
spec:
  replicas: 1
  selector:
    matchLabels:
      app: opa
  template:
    metadata:
      labels:
        app: opa
    spec:
      containers:
        - name: opa
          image: openpolicyagent/opa:latest-envoy
          securityContext:
            runAsUser: 1111
          volumeMounts:
          - readOnly: true
            mountPath: /policy
            name: opa-policy
          args:
          - "run"
          - "--server"
          - "--addr=localhost:8181"
          - "--diagnostic-addr=0.0.0.0:8282"
          - "--set=plugins.envoy_ext_authz_grpc.addr=:9191"
          - "--set=plugins.envoy_ext_authz_grpc.query=data.envoy.authz.allow"
          - "--set=decision_logs.console=true"
          - "--ignore=.*"
          - "/policy/policy.rego"
          ports:
          - containerPort: 9191
          livenessProbe:
            httpGet:
              path: /health?plugins
              scheme: HTTP
              port: 8282
            initialDelaySeconds: 5
            periodSeconds: 5
          readinessProbe:
            httpGet:
              path: /health?plugins
              scheme: HTTP
              port: 8282
            initialDelaySeconds: 5
            periodSeconds: 5
      volumes:
        - name: proxy-config
          configMap:
            name: proxy-config
        - name: opa-policy
          secret:
            secretName: opa-policy
EOF

还要部署httpbin:

kubectl apply -f samples/httpbin/httpbin.yaml

定义外部授权者

运行以下命令以编辑meshconfig:

kubectl edit configmap istio -n istio-system

将以下内容添加extensionProviders到中meshconfig:

apiVersion: v1
data:
  mesh: |-
    # Add the following contents:
    extensionProviders:
    - name: "opa.default"
      envoyExtAuthzGrpc:
        service: "opa.default.svc.cluster.local"
        port: "9191"

使用CUSTOM action创建AuthorizationPolicy

运行以下命令以创建授权策略,以在除/ip以下路径之外的所有路径上启用外部授权:

$ kubectl apply -f - <<EOF
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: httpbin-opa
spec:
  selector:
    matchLabels:
      app: httpbin
  action: CUSTOM
  provider:
    name: "opa.default"
  rules:
  - to:
    - operation:
        notPaths: ["/ip"]
EOF

测试OPA政策

  • 创建一个客户端窗格来发送请求:

$ kubectl apply -f samples/sleep/sleep.yaml
$ export SLEEP_POD=$(kubectl get pod -l app=sleep -o jsonpath={.items..metadata.name})
  • 使用由OPA签名的测试JWT令牌:

$ export TOKEN_PATH_HEADERS="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwYXRoIjoiTDJobFlXUmxjbk09IiwibmJmIjoxNTAwMDAwMDAwLCJleHAiOjE5MDAwMDAwMDB9.9yl8LcZdq-5UpNLm0Hn0nnoBHXXAnK4e8RSl9vn6l98"
  • 测试JWT令牌具有以下claims:

{
  "path": "L2hlYWRlcnM=",
  "nbf": 1500000000,
  "exp": 1900000000
}

path claim 要求有值L2hlYWRlcnM=,其是/headers的BASE64编码。

  • 将请求发送到/headers没有令牌的路径。应该使用403拒绝它,因为没有JWT令牌:

$ kubectl exec ${SLEEP_POD} -c sleep  -- curl http://httpbin-with-opa:8000/headers -s -o /dev/null -w "%{http_code}\n"
403
  • /get使用有效令牌向路径发送请求。这应该用403拒绝,因为路径/get与令牌不匹配/headers:

$ kubectl exec ${SLEEP_POD} -c sleep  -- curl http://httpbin-with-opa:8000/get -H "Authorization: Bearer $TOKEN_PATH_HEADERS" -s -o /dev/null -w "%{http_code}\n"
403
  • /headers使用有效令牌向路径发送请求。应该使用200,因为路径与令牌匹配:

$ kubectl exec ${SLEEP_POD} -c sleep  -- curl http://httpbin-with-opa:8000/headers -H "Authorization: Bearer $TOKEN_PATH_HEADERS" -s -o /dev/null -w "%{http_code}\n"
200
  • 将请求发送到/ip没有令牌的路径。应该允许使用200,因为该路径/ip已排除在授权范围之外:

$ kubectl exec ${SLEEP_POD} -c sleep  -- curl http://httpbin-with-opa:8000/ip -s -o /dev/null -w "%{http_code}\n"
200
  • 检查代理和OPA日志以确认结果。

概括

在Istio 1.9中,CUSTOM授权策略中的action使您可以轻松地将Istio与任何外部授权系统集成,具有以下优点:

  • 授权策略API中的一流支持

  • 易用性:只需使用URL定义外部授权者,并使用授权策略启用,再也不用担心EnvoyFilterAPI

  • 条件触发,可提高性能

  • 支持外部授权者的各种部署类型:

    • 具有或不具有代理的普通服务和Pod

    • 在工作负载窗格中作为单独的容器

    • 外网

扫描关注我:

Last updated