より良い外部認証

AuthorizationPolicyが、認証を外部システムに委任するCUSTOMアクションをサポートするようになりました。

2021年2月9日 | By Yangmin Zhu - Google

背景

Istioの認証ポリシーは、メッシュ内のサービスへのアクセス制御を提供します。高速で強力であり、広く使用されている機能です。Istio 1.4での最初のリリース以降、DENYアクション除外セマンティクスX-Forwarded-ForヘッダーのサポートネストされたJWTクレームのサポートなど、ポリシーをより柔軟にするための継続的な改善を行ってきました。これらの機能により、認証ポリシーの柔軟性が向上しましたが、このモデルではまだサポートできないユースケースが多くあります。例えば、次のような場合です。

ソリューション

Istio 1.9では、アクセス制御の決定を外部認証サービスに委任できるようにするCUSTOMアクションを導入することで、認証ポリシーに拡張性を実装しました。

CUSTOMアクションを使用すると、独自のカスタム認証ロジックを実装する外部認証システムとIstioを統合できます。次の図は、この統合のハイレベルなアーキテクチャを示しています。

External Authorization Architecture
外部認証アーキテクチャ

構成時に、メッシュ管理者は、プロキシ(ゲートウェイまたはサイドカー)で外部認証を有効にするために、CUSTOMアクションを持つ認証ポリシーを構成します。管理者は、外部認証サービスが起動して実行されていることを確認する必要があります。

実行時:

  1. リクエストはプロキシによってインターセプトされ、プロキシは認証ポリシーでユーザーによって構成されたとおりに、外部認証サービスにチェックリクエストを送信します。

  2. 外部認証サービスは、許可するかどうかを決定します。

  3. 許可された場合、リクエストは続行され、ALLOW/DENYアクションで定義されたローカル認証によって適用されます。

  4. 拒否された場合、リクエストはすぐに拒否されます。

CUSTOMアクションを含む認証ポリシーの例を見てみましょう。

apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: ext-authz
  namespace: istio-system
spec:
  # The selector applies to the ingress gateway in the istio-system namespace.
  selector:
    matchLabels:
      app: istio-ingressgateway
  # The action "CUSTOM" delegates the access control to an external authorizer, this is different from
  # the ALLOW/DENY action that enforces the access control right inside the proxy.
  action: CUSTOM
  # The provider specifies the name of the external authorizer defined in the meshconfig, which tells where and how to
  # talk to the external auth service. We will cover this more later.
  provider:
    name: "my-ext-authz-service"
  # The rule specifies that the access control is triggered only if the request path has the prefix "/admin/".
  # This allows you to easily enable or disable the external authorization based on the requests, avoiding the external
  # check request if it is not needed.
  rules:
  - to:
    - operation:
        paths: ["/admin/*"]

これは、メッシュ構成で定義されているmy-ext-authz-serviceというプロバイダーを参照しています。

extensionProviders:
# The name "my-ext-authz-service" is referred to by the authorization policy in its provider field.
- name: "my-ext-authz-service"
  # The "envoyExtAuthzGrpc" field specifies the type of the external authorization service is implemented by the Envoy
  # ext-authz filter gRPC API. The other supported type is the Envoy ext-authz filter HTTP API.
  # See more in https://www.envoyproxy.io/docs/envoy/v1.16.2/intro/arch_overview/security/ext_authz_filter.
  envoyExtAuthzGrpc:
    # The service and port specifies the address of the external auth service, "ext-authz.istio-system.svc.cluster.local"
    # means the service is deployed in the mesh. It can also be defined out of the mesh or even inside the pod as a separate
    # container.
    service: "ext-authz.istio-system.svc.cluster.local"
    port: 9000

CUSTOMアクションの認証ポリシーは、実行時に外部認証を有効にします。他のアクションですでに使用しているのと同じルールを使用して、リクエストに基づいて外部認証を条件付きでトリガーするように構成できます。

外部認証サービスは、現在meshconfig APIで定義されており、名前で参照されます。プロキシの有無にかかわらず、メッシュにデプロイできます。プロキシを使用する場合は、さらにPeerAuthenticationを使用して、プロキシと外部認証サービス間のmTLSを有効にすることができます。

CUSTOMアクションは現在、実験段階にあります。APIは、ユーザーのフィードバックに基づいて、後方互換性のない方法で変更される可能性があります。認証ポリシーのルールは、現在、CUSTOMアクションで使用する場合、認証フィールド(例:ソースプリンシパルまたはJWTクレーム)をサポートしていません。特定のワークロードに対して許可されるプロバイダーは1つだけですが、異なるワークロードでは異なるプロバイダーを使用できます。

詳細については、Better External Authorization設計ドキュメントを参照してください。

OPAの例

このセクションでは、イングレスゲートウェイ上の外部認証者としてOpen Policy AgentでCUSTOMアクションを使用する方法を説明します。/ipを除くすべてのパスで外部認証を条件付きで有効にします。

サンプルext-authzサーバーを使用する、より基本的な入門については、外部認証タスクを参照することもできます。

OPAポリシーの例を作成する

次のコマンドを実行して、パスのプレフィックスがJWTトークンのクレーム「path」(base64エンコード)と一致する場合にリクエストを許可するOPAポリシーを作成します。

$ 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をデプロイする

サイドカーインジェクションを有効にする

$ kubectl label ns default istio-injection=enabled

次のコマンドを実行して、サンプルアプリケーションhttpbinとOPAをデプロイします。OPAは、httpbinポッド内の別のコンテナとして、または完全に別のポッドにデプロイできます。

$ kubectl apply -f - <<EOF
apiVersion: v1
kind: Service
metadata:
  name: httpbin-with-opa
  labels:
    app: httpbin-with-opa
    service: httpbin-with-opa
spec:
  ports:
  - name: http
    port: 8000
    targetPort: 80
  selector:
    app: httpbin-with-opa
---
# Define the service entry for the local OPA service on port 9191.
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
  name: local-opa-grpc
spec:
  hosts:
  - "local-opa-grpc.local"
  endpoints:
  - address: "127.0.0.1"
  ports:
  - name: grpc
    number: 9191
    protocol: GRPC
  resolution: STATIC
---
kind: Deployment
apiVersion: apps/v1
metadata:
  name: httpbin-with-opa
  labels:
    app: httpbin-with-opa
spec:
  replicas: 1
  selector:
    matchLabels:
      app: httpbin-with-opa
  template:
    metadata:
      labels:
        app: httpbin-with-opa
    spec:
      containers:
        - image: docker.io/kennethreitz/httpbin
          imagePullPolicy: IfNotPresent
          name: httpbin
          ports:
          - containerPort: 80
        - 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"
          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

外部認証者を定義する

次のコマンドを実行して、meshconfigを編集します。

$ kubectl edit configmap istio -n istio-system

次のextensionProvidersmeshconfigに追加します。

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

CUSTOMアクションを持つAuthorizationPolicyを作成する

次のコマンドを実行して、/ipを除くすべてのパスで外部認証を有効にする認証ポリシーを作成します。

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

OPAポリシーをテストする

  1. リクエストを送信するクライアントポッドを作成する

    Zip
    $ kubectl apply -f @samples/sleep/sleep.yaml@
    $ export SLEEP_POD=$(kubectl get pod -l app=sleep -o jsonpath={.items..metadata.name})
    
  2. OPAによって署名されたテストJWTトークンを使用する

    $ export TOKEN_PATH_HEADERS="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwYXRoIjoiTDJobFlXUmxjbk09IiwibmJmIjoxNTAwMDAwMDAwLCJleHAiOjE5MDAwMDAwMDB9.9yl8LcZdq-5UpNLm0Hn0nnoBHXXAnK4e8RSl9vn6l98"
    

    テストJWTトークンには、次のクレームがあります。

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

    pathクレームの値はL2hlYWRlcnM=であり、これは/headersのbase64エンコードです。

  3. トークンなしでパス/headersにリクエストを送信します。これはJWTトークンがないため、403で拒否されるはずです。

    $ kubectl exec ${SLEEP_POD} -c sleep  -- curl http://httpbin-with-opa:8000/headers -s -o /dev/null -w "%{http_code}\n"
    403
    

  4. 有効なトークンでパス/getにリクエストを送信します。パス/getがトークン/headersと一致しないため、これは403で拒否されるはずです。

    $ 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
    
  5. 有効なトークンでパス/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
    
  6. トークンなしでパス/ipにリクエストを送信します。パス/ipが認証から除外されているため、これは200で許可されるはずです。

    $ kubectl exec ${SLEEP_POD} -c sleep  -- curl http://httpbin-with-opa:8000/ip -s -o /dev/null -w "%{http_code}\n"
    200
    
  7. プロキシとOPAのログを確認して、結果を確認します。

概要

Istio 1.9では、認証ポリシーのCUSTOMアクションを使用すると、次の利点を利用して、任意の外部認証システムとIstioを簡単に統合できます。

次のバージョンでこの機能をより安定した段階に昇格させるために取り組んでおり、discuss.istio.ioで皆様のフィードバックをお待ちしております。

謝辞

このブログの草稿をレビューしてくれたCraig BoxChristian PostaLimin Wangに感謝します。

この投稿を共有する