より良い外部認証
AuthorizationPolicyが、認証を外部システムに委任するCUSTOMアクションをサポートするようになりました。
背景
Istioの認証ポリシーは、メッシュ内のサービスへのアクセス制御を提供します。高速で強力であり、広く使用されている機能です。Istio 1.4での最初のリリース以降、DENY
アクション、除外セマンティクス、X-Forwarded-For
ヘッダーのサポート、ネストされたJWTクレームのサポートなど、ポリシーをより柔軟にするための継続的な改善を行ってきました。これらの機能により、認証ポリシーの柔軟性が向上しましたが、このモデルではまだサポートできないユースケースが多くあります。例えば、次のような場合です。
認証ポリシーに簡単に移行できない、または簡単に置き換えることができない独自の社内認証システムがある。
Open Policy Agentや
oauth2
プロキシなどのサードパーティソリューションと統合したいが、Istioの低レベルEnvoy構成APIの使用が必要な場合や、まったく不可能な場合がある。認証ポリシーに、ユースケースに必要なセマンティクスが欠けている。
ソリューション
Istio 1.9では、アクセス制御の決定を外部認証サービスに委任できるようにするCUSTOM
アクションを導入することで、認証ポリシーに拡張性を実装しました。
CUSTOM
アクションを使用すると、独自のカスタム認証ロジックを実装する外部認証システムとIstioを統合できます。次の図は、この統合のハイレベルなアーキテクチャを示しています。
構成時に、メッシュ管理者は、プロキシ(ゲートウェイまたはサイドカー)で外部認証を有効にするために、CUSTOM
アクションを持つ認証ポリシーを構成します。管理者は、外部認証サービスが起動して実行されていることを確認する必要があります。
実行時:
リクエストはプロキシによってインターセプトされ、プロキシは認証ポリシーでユーザーによって構成されたとおりに、外部認証サービスにチェックリクエストを送信します。
外部認証サービスは、許可するかどうかを決定します。
許可された場合、リクエストは続行され、
ALLOW
/DENY
アクションで定義されたローカル認証によって適用されます。拒否された場合、リクエストはすぐに拒否されます。
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
$ 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.local"
envoyExtAuthzGrpc:
service: "local-opa-grpc.local"
port: "9191"
apiVersion: v1
data:
mesh: |-
# Add the following contents:
extensionProviders:
- name: "opa.default"
envoyExtAuthzGrpc:
service: "opa.default.svc.cluster.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
$ 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トークンには、次のクレームがあります。
{ "path": "L2hlYWRlcnM=", "nbf": 1500000000, "exp": 1900000000 }
path
クレームの値はL2hlYWRlcnM=
であり、これは/headers
のbase64エンコードです。トークンなしでパス
/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
$ kubectl exec ${SLEEP_POD} -c sleep -- curl http://httpbin:8000/headers -s -o /dev/null -w "%{http_code}\n" 403
有効なトークンでパス
/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
$ kubectl exec ${SLEEP_POD} -c sleep -- curl http://httpbin: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
$ kubectl exec ${SLEEP_POD} -c sleep -- curl http://httpbin:8000/headers -H "Authorization: Bearer $TOKEN_PATH_HEADERS" -s -o /dev/null -w "%{http_code}\n" 200
トークンなしでパス
/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
$ kubectl exec ${SLEEP_POD} -c sleep -- curl http://httpbin:8000/ip -s -o /dev/null -w "%{http_code}\n" 200
プロキシとOPAのログを確認して、結果を確認します。
概要
Istio 1.9では、認証ポリシーのCUSTOM
アクションを使用すると、次の利点を利用して、任意の外部認証システムとIstioを簡単に統合できます。
認証ポリシーAPIでのファーストクラスのサポート
使いやすさ:URLで外部認証者を簡単に定義し、認証ポリシーで有効にするだけで、
EnvoyFilter
APIの手間はもう必要ありません。条件付きトリガー、パフォーマンスの向上を可能にする
外部認証者のさまざまなデプロイタイプをサポート
プロキシの有無にかかわらず、通常のサービスとポッド
別のコンテナとしてワークロードポッド内
メッシュ外
次のバージョンでこの機能をより安定した段階に昇格させるために取り組んでおり、discuss.istio.ioで皆様のフィードバックをお待ちしております。
謝辞
このブログの草稿をレビューしてくれたCraig Box
、Christian Posta
、Limin Wang
に感謝します。