認証ポリシー

このタスクでは、Istio認証ポリシーの有効化、構成、使用時に実行する必要がある主要なアクティビティについて説明します。基礎となる概念の詳細については、認証の概要をご覧ください。

始める前に

$ istioctl install --set profile=default

設定

これらの例では、`foo`と`bar`の2つの名前空間を使用し、それぞれにEnvoyプロキシで実行される`httpbin`と`curl`という2つのサービスがあります。また、サイドカーなしで`legacy`名前空間で実行される`httpbin`と`curl`の第2インスタンスも使用します。タスクを試行するときに同じ例を使用する場合は、次のコマンドを実行してください。

ZipZipZipZipZipZip
$ kubectl create ns foo
$ kubectl apply -f <(istioctl kube-inject -f @samples/httpbin/httpbin.yaml@) -n foo
$ kubectl apply -f <(istioctl kube-inject -f @samples/curl/curl.yaml@) -n foo
$ kubectl create ns bar
$ kubectl apply -f <(istioctl kube-inject -f @samples/httpbin/httpbin.yaml@) -n bar
$ kubectl apply -f <(istioctl kube-inject -f @samples/curl/curl.yaml@) -n bar
$ kubectl create ns legacy
$ kubectl apply -f @samples/httpbin/httpbin.yaml@ -n legacy
$ kubectl apply -f @samples/curl/curl.yaml@ -n legacy

設定の検証は、名前空間`foo`、`bar`、または`legacy`内の任意の`curl` podから、`httpbin.foo`、`httpbin.bar`、または`httpbin.legacy`のいずれかに`curl`を使用してHTTPリクエストを送信することで行うことができます。すべてのリクエストはHTTPコード200で成功する必要があります。

例えば、`curl.bar`から`httpbin.foo`への到達可能性をチェックするコマンドを以下に示します。

$ kubectl exec "$(kubectl get pod -l app=curl -n bar -o jsonpath={.items..metadata.name})" -c curl -n bar -- curl http://httpbin.foo:8000/ip -s -o /dev/null -w "%{http_code}\n"
200

このワンライナーコマンドは、すべての到達可能性の組み合わせを簡単に反復処理します。

$ for from in "foo" "bar" "legacy"; do for to in "foo" "bar" "legacy"; do kubectl exec "$(kubectl get pod -l app=curl -n ${from} -o jsonpath={.items..metadata.name})" -c curl -n ${from} -- curl -s "http://httpbin.${to}:8000/ip" -s -o /dev/null -w "curl.${from} to httpbin.${to}: %{http_code}\n"; done; done
curl.foo to httpbin.foo: 200
curl.foo to httpbin.bar: 200
curl.foo to httpbin.legacy: 200
curl.bar to httpbin.foo: 200
curl.bar to httpbin.bar: 200
curl.bar to httpbin.legacy: 200
curl.legacy to httpbin.foo: 200
curl.legacy to httpbin.bar: 200
curl.legacy to httpbin.legacy: 200

以下のコマンドを使用して、システムにピア認証ポリシーが存在しないことを確認してください。

$ kubectl get peerauthentication --all-namespaces
No resources found

最後に、例として示したサービスに適用される宛先ルールがないことを確認してください。既存の宛先ルールの`host:`値をチェックし、一致しないことを確認することで、これを行うことができます。例えば

$ kubectl get destinationrules.networking.istio.io --all-namespaces -o yaml | grep "host:"

自動相互TLS

デフォルトでは、IstioはIstioプロキシに移行されたサーバーワークロードを追跡し、クライアントプロキシがそれらのワークロードに自動的に相互TLSトラフィックを送信し、サイドカーのないワークロードにはプレーンテキストトラフィックを送信するように構成します。

したがって、プロキシを持つワークロード間のすべてのトラフィックは、何もせずに相互TLSを使用します。例えば、`httpbin/header`へのリクエストからのレスポンスを見てみましょう。相互TLSを使用する場合、プロキシはバックエンドへのアップストリームリクエストに`X-Forwarded-Client-Cert`ヘッダーを挿入します。そのヘッダーの存在は、相互TLSが使用されている証拠です。例えば

$ kubectl exec "$(kubectl get pod -l app=curl -n foo -o jsonpath={.items..metadata.name})" -c curl -n foo -- curl -s http://httpbin.foo:8000/headers -s | jq '.headers["X-Forwarded-Client-Cert"][0]' | sed 's/Hash=[a-z0-9]*;/Hash=<redacted>;/'
  "By=spiffe://cluster.local/ns/foo/sa/httpbin;Hash=<redacted>;Subject=\"\";URI=spiffe://cluster.local/ns/foo/sa/curl"

サーバーにサイドカーがない場合、`X-Forwarded-Client-Cert`ヘッダーは存在せず、リクエストがプレーンテキストであることを意味します。

$ kubectl exec "$(kubectl get pod -l app=curl -n foo -o jsonpath={.items..metadata.name})" -c curl -n foo -- curl http://httpbin.legacy:8000/headers -s | grep X-Forwarded-Client-Cert

厳格モードでのIstio相互TLSのグローバル有効化

Istioはプロキシとワークロード間のすべてのトラフィックを相互TLSに自動的にアップグレードしますが、ワークロードは依然としてプレーンテキストトラフィックを受信できます。メッシュ全体の非相互TLSトラフィックを防ぐには、相互TLSモードが`STRICT`に設定されたメッシュ全体のピア認証ポリシーを設定します。メッシュ全体のピア認証ポリシーには`selector`を含める必要がなく、**ルート名前空間**に適用する必要があります。例えば

$ kubectl apply -f - <<EOF
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
  name: "default"
  namespace: "istio-system"
spec:
  mtls:
    mode: STRICT
EOF

このピア認証ポリシーは、TLSで暗号化されたリクエストのみを受け入れるようにワークロードを構成します。`selector`フィールドの値を指定していないため、このポリシーはメッシュ内のすべてのワークロードに適用されます。

テストコマンドをもう一度実行してください。

$ for from in "foo" "bar" "legacy"; do for to in "foo" "bar" "legacy"; do kubectl exec "$(kubectl get pod -l app=curl -n ${from} -o jsonpath={.items..metadata.name})" -c curl -n ${from} -- curl "http://httpbin.${to}:8000/ip" -s -o /dev/null -w "curl.${from} to httpbin.${to}: %{http_code}\n"; done; done
curl.foo to httpbin.foo: 200
curl.foo to httpbin.bar: 200
curl.foo to httpbin.legacy: 200
curl.bar to httpbin.foo: 200
curl.bar to httpbin.bar: 200
curl.bar to httpbin.legacy: 200
curl.legacy to httpbin.foo: 000
command terminated with exit code 56
curl.legacy to httpbin.bar: 000
command terminated with exit code 56
curl.legacy to httpbin.legacy: 200

プロキシを持たないクライアント`curl.legacy`からプロキシを持つサーバー`httpbin.foo`または`httpbin.bar`へのリクエストを除き、リクエストは依然として成功します。これは、相互TLSが厳密に要求されるようになったため、サイドカーのないワークロードが準拠できないことが原因で予想される動作です。

クリーンアップパート1

セッションで追加されたグローバル認証ポリシーを削除します。

$ kubectl delete peerauthentication -n istio-system default

名前空間またはワークロードごとの相互TLSの有効化

名前空間全体のポリシー

特定の名前空間内のすべてのワークロードの相互TLSを変更するには、名前空間全体のポリシーを使用します。ポリシーの仕様はメッシュ全体のポリシーと同じですが、`metadata`の下に適用する名前空間を指定します。例えば、以下のピア認証ポリシーは`foo`名前空間に対して厳格な相互TLSを有効にします。

$ kubectl apply -f - <<EOF
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
  name: "default"
  namespace: "foo"
spec:
  mtls:
    mode: STRICT
EOF

このポリシーは`foo`名前空間のワークロードのみに適用されるため、サイドカーなしのクライアント(`curl.legacy`)から`httpbin.foo`へのリクエストのみが失敗し始めるはずです。

$ for from in "foo" "bar" "legacy"; do for to in "foo" "bar" "legacy"; do kubectl exec "$(kubectl get pod -l app=curl -n ${from} -o jsonpath={.items..metadata.name})" -c curl -n ${from} -- curl "http://httpbin.${to}:8000/ip" -s -o /dev/null -w "curl.${from} to httpbin.${to}: %{http_code}\n"; done; done
curl.foo to httpbin.foo: 200
curl.foo to httpbin.bar: 200
curl.foo to httpbin.legacy: 200
curl.bar to httpbin.foo: 200
curl.bar to httpbin.bar: 200
curl.bar to httpbin.legacy: 200
curl.legacy to httpbin.foo: 000
command terminated with exit code 56
curl.legacy to httpbin.bar: 200
curl.legacy to httpbin.legacy: 200

ワークロードごとの相互TLSの有効化

特定のワークロードに対してピア認証ポリシーを設定するには、`selector`セクションを構成し、目的のワークロードに一致するラベルを指定する必要があります。例えば、以下のピア認証ポリシーは`httpbin.bar`ワークロードに対して厳格な相互TLSを有効にします。

$ cat <<EOF | kubectl apply -n bar -f -
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
  name: "httpbin"
  namespace: "bar"
spec:
  selector:
    matchLabels:
      app: httpbin
  mtls:
    mode: STRICT
EOF

再び、プローブコマンドを実行します。予想通り、`curl.legacy`から`httpbin.bar`へのリクエストは同じ理由で失敗し始めます。

$ for from in "foo" "bar" "legacy"; do for to in "foo" "bar" "legacy"; do kubectl exec "$(kubectl get pod -l app=curl -n ${from} -o jsonpath={.items..metadata.name})" -c curl -n ${from} -- curl "http://httpbin.${to}:8000/ip" -s -o /dev/null -w "curl.${from} to httpbin.${to}: %{http_code}\n"; done; done
curl.foo to httpbin.foo: 200
curl.foo to httpbin.bar: 200
curl.foo to httpbin.legacy: 200
curl.bar to httpbin.foo: 200
curl.bar to httpbin.bar: 200
curl.bar to httpbin.legacy: 200
curl.legacy to httpbin.foo: 000
command terminated with exit code 56
curl.legacy to httpbin.bar: 000
command terminated with exit code 56
curl.legacy to httpbin.legacy: 200
...
curl.legacy to httpbin.bar: 000
command terminated with exit code 56

ポートごとに相互TLSの設定を調整するには、`portLevelMtls`セクションを構成する必要があります。例えば、以下のピア認証ポリシーは、ポート`8080`を除くすべてのポートで相互TLSを要求します。

$ cat <<EOF | kubectl apply -n bar -f -
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
  name: "httpbin"
  namespace: "bar"
spec:
  selector:
    matchLabels:
      app: httpbin
  mtls:
    mode: STRICT
  portLevelMtls:
    8080:
      mode: DISABLE
EOF
  1. ピア認証ポリシーのポート値は、コンテナのポートです。
  2. ポートがサービスにバインドされている場合のみ、`portLevelMtls`を使用できます。そうでない場合は、Istioによって無視されます。
$ for from in "foo" "bar" "legacy"; do for to in "foo" "bar" "legacy"; do kubectl exec "$(kubectl get pod -l app=curl -n ${from} -o jsonpath={.items..metadata.name})" -c curl -n ${from} -- curl "http://httpbin.${to}:8000/ip" -s -o /dev/null -w "curl.${from} to httpbin.${to}: %{http_code}\n"; done; done
curl.foo to httpbin.foo: 200
curl.foo to httpbin.bar: 200
curl.foo to httpbin.legacy: 200
curl.bar to httpbin.foo: 200
curl.bar to httpbin.bar: 200
curl.bar to httpbin.legacy: 200
curl.legacy to httpbin.foo: 000
command terminated with exit code 56
curl.legacy to httpbin.bar: 200
curl.legacy to httpbin.legacy: 200

ポリシーの優先順位

ワークロード固有のピア認証ポリシーは、名前空間全体のポリシーよりも優先されます。例えば、`httpbin.foo`ワークロードに対して相互TLSを無効にするポリシーを追加することで、この動作をテストできます。`foo`名前空間内のすべてのサービスに対して相互TLSを有効にする名前空間全体のポリシーを既に作成しており、`curl.legacy`から`httpbin.foo`へのリクエストが失敗していることに注意してください(上記参照)。

$ cat <<EOF | kubectl apply -n foo -f -
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
  name: "overwrite-example"
  namespace: "foo"
spec:
  selector:
    matchLabels:
      app: httpbin
  mtls:
    mode: DISABLE
EOF

`curl.legacy`からのリクエストを再実行すると、成功の戻りコード(200)が再び表示され、サービス固有のポリシーが名前空間全体のポリシーをオーバーライドすることを確認します。

$ kubectl exec "$(kubectl get pod -l app=curl -n legacy -o jsonpath={.items..metadata.name})" -c curl -n legacy -- curl http://httpbin.foo:8000/ip -s -o /dev/null -w "%{http_code}\n"
200

クリーンアップパート2

上記のステップで作成されたポリシーを削除します。

$ kubectl delete peerauthentication default overwrite-example -n foo
$ kubectl delete peerauthentication httpbin -n bar

エンドユーザー認証

この機能を試すには、有効なJWTが必要です。JWTは、デモで使用したいJWKSエンドポイントに対応している必要があります。このチュートリアルでは、IstioコードベースからのテストトークンJWTテストJWKSエンドポイントを使用します。

また、便宜上、イングレスゲートウェイを介して`httpbin.foo`を公開します(詳細については、イングレスタスクを参照してください)。

ゲートウェイの構成

Zip
$ kubectl apply -f @samples/httpbin/httpbin-gateway.yaml@ -n foo

イングレスIPとポートの決定の手順に従って、`INGRESS_PORT`と`INGRESS_HOST`環境変数を定義します。

ゲートウェイを介してテストクエリを実行します。

$ curl "$INGRESS_HOST:$INGRESS_PORT/headers" -s -o /dev/null -w "%{http_code}\n"
200

次に、イングレスゲートウェイに対してエンドユーザーJWTを要求するリクエスト認証ポリシーを追加します。

$ kubectl apply -f - <<EOF
apiVersion: security.istio.io/v1
kind: RequestAuthentication
metadata:
  name: "jwt-example"
  namespace: istio-system
spec:
  selector:
    matchLabels:
      istio: ingressgateway
  jwtRules:
  - issuer: "testing@secure.istio.io"
    jwksUri: "https://raw.githubusercontent.com/istio/istio/release-1.24/security/tools/jwt/samples/jwks.json"
EOF

それを選択するワークロードの名前空間、この場合はイングレスゲートウェイにポリシーを適用します。

承認ヘッダーにトークンを提供すると、その暗黙的なデフォルトの場所であるIstioは公開鍵セットを使用してトークンを検証し、ベアラートークンが無効な場合はリクエストを拒否します。ただし、トークンがないリクエストは受け入れられます。この動作を確認するには、トークンなしで、無効なトークンで、有効なトークンでリクエストを再試行してください。

$ curl "$INGRESS_HOST:$INGRESS_PORT/headers" -s -o /dev/null -w "%{http_code}\n"
200
$ curl --header "Authorization: Bearer deadbeef" "$INGRESS_HOST:$INGRESS_PORT/headers" -s -o /dev/null -w "%{http_code}\n"
401
$ TOKEN=$(curl https://raw.githubusercontent.com/istio/istio/release-1.24/security/tools/jwt/samples/demo.jwt -s)
$ curl --header "Authorization: Bearer $TOKEN" "$INGRESS_HOST:$INGRESS_PORT/headers" -s -o /dev/null -w "%{http_code}\n"
200

JWT検証の他の側面を確認するには、スクリプトgen-jwt.pyを使用して、異なる発行者、対象者、有効期限などを使用して新しいトークンを生成してテストします。このスクリプトはIstioリポジトリからダウンロードできます。

$ wget --no-verbose https://raw.githubusercontent.com/istio/istio/release-1.24/security/tools/jwt/samples/gen-jwt.py

`key.pem`ファイルも必要です。

$ wget --no-verbose https://raw.githubusercontent.com/istio/istio/release-1.24/security/tools/jwt/samples/key.pem

JWT認証には60秒のクロックスキューがあります。つまり、JWTトークンは設定された`nbf`よりも60秒早く有効になり、設定された`exp`の後60秒間有効のままになります。

例えば、以下のコマンドは5秒後に期限切れになるトークンを作成します。ご覧のように、Istioは最初にそのトークンを使用してリクエストを正常に認証しますが、65秒後に拒否します。

$ TOKEN=$(python3 ./gen-jwt.py ./key.pem --expire 5)
$ for i in $(seq 1 10); do curl --header "Authorization: Bearer $TOKEN" "$INGRESS_HOST:$INGRESS_PORT/headers" -s -o /dev/null -w "%{http_code}\n"; sleep 10; done
200
200
200
200
200
200
200
401
401
401

イングレスゲートウェイ(例:サービス`istio-ingressgateway.istio-system.svc.cluster.local`)にJWTポリシーを追加することもできます。これは、個々のサービスではなく、ゲートウェイにバインドされているすべてのサービスに対してJWTポリシーを定義するために使用されることがよくあります。

有効なトークンの要求

有効なトークンがないリクエストを拒否するには、リクエストプリンシパルがないリクエストに対して`DENY`アクションを指定するルールを持つ承認ポリシーを追加します。これは、以下の例では`notRequestPrincipals: ["*"]`として示されています。リクエストプリンシパルは、有効なJWTトークンが提供されている場合のみ利用できます。そのため、このルールは有効なトークンがないリクエストを拒否します。

$ kubectl apply -f - <<EOF
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
  name: "frontend-ingress"
  namespace: istio-system
spec:
  selector:
    matchLabels:
      istio: ingressgateway
  action: DENY
  rules:
  - from:
    - source:
        notRequestPrincipals: ["*"]
EOF

トークンなしでリクエストを再試行します。リクエストはエラーコード`403`で失敗します。

$ curl "$INGRESS_HOST:$INGRESS_PORT/headers" -s -o /dev/null -w "%{http_code}\n"
403

パスごとの有効なトークンの要求

ホスト、パス、またはメソッドごとにトークン要件を使用して承認を調整するには、承認ポリシーを変更して`/headers`でのみJWTを要求するようにします。この承認ルールが有効になると、`$INGRESS_HOST:$INGRESS_PORT/headers`へのリクエストはエラーコード`403`で失敗します。他のすべてのパスへのリクエスト(例:`$INGRESS_HOST:$INGRESS_PORT/ip`)は成功します。

$ kubectl apply -f - <<EOF
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
  name: "frontend-ingress"
  namespace: istio-system
spec:
  selector:
    matchLabels:
      istio: ingressgateway
  action: DENY
  rules:
  - from:
    - source:
        notRequestPrincipals: ["*"]
    to:
    - operation:
        paths: ["/headers"]
EOF
$ curl "$INGRESS_HOST:$INGRESS_PORT/headers" -s -o /dev/null -w "%{http_code}\n"
403
$ curl "$INGRESS_HOST:$INGRESS_PORT/ip" -s -o /dev/null -w "%{http_code}\n"
200

クリーンアップパート3

  1. 認証ポリシーの削除

    $ kubectl -n istio-system delete requestauthentication jwt-example
    
  2. 承認ポリシーの削除

    $ kubectl -n istio-system delete authorizationpolicy frontend-ingress
    
  3. トークンジェネレータースクリプトとキーファイルの削除

    $ rm -f ./gen-jwt.py ./key.pem
    
  4. 後続のタスクを実行しない場合は、テスト名前空間を削除するだけで、すべてのリソースを削除できます。

    $ kubectl delete ns foo bar legacy
    
この情報は役に立ちましたか?
改善のための提案はありますか?

ご意見ありがとうございます!