認証ポリシー
このタスクでは、Istio認証ポリシーの有効化、構成、使用時に実行する必要がある主要なアクティビティについて説明します。基礎となる概念の詳細については、認証の概要をご覧ください。
始める前に
インストール手順に記載されているように、`default`構成プロファイルを使用して、KubernetesクラスタにIstioをインストールします。
$ istioctl install --set profile=default
設定
これらの例では、`foo`と`bar`の2つの名前空間を使用し、それぞれにEnvoyプロキシで実行される`httpbin`と`curl`という2つのサービスがあります。また、サイドカーなしで`legacy`名前空間で実行される`httpbin`と`curl`の第2インスタンスも使用します。タスクを試行するときに同じ例を使用する場合は、次のコマンドを実行してください。
$ 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
- ピア認証ポリシーのポート値は、コンテナのポートです。
- ポートがサービスにバインドされている場合のみ、`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`を公開します(詳細については、イングレスタスクを参照してください)。
ゲートウェイの構成
$ kubectl apply -f @samples/httpbin/httpbin-gateway.yaml@ -n foo
イングレスIPとポートの決定の手順に従って、`INGRESS_PORT`と`INGRESS_HOST`環境変数を定義します。
ゲートウェイの作成
$ kubectl apply -f @samples/httpbin/gateway-api/httpbin-gateway.yaml@ -n foo
$ kubectl wait --for=condition=programmed gtw -n foo httpbin-gateway
`INGRESS_PORT`と`INGRESS_HOST`環境変数を設定します。
$ export INGRESS_HOST=$(kubectl get gtw httpbin-gateway -n foo -o jsonpath='{.status.addresses[0].value}')
$ export INGRESS_PORT=$(kubectl get gtw httpbin-gateway -n foo -o jsonpath='{.spec.listeners[?(@.name=="http")].port}')
ゲートウェイを介してテストクエリを実行します。
$ 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
$ kubectl apply -f - <<EOF
apiVersion: security.istio.io/v1
kind: RequestAuthentication
metadata:
name: "jwt-example"
namespace: foo
spec:
targetRef:
kind: Gateway
group: gateway.networking.k8s.io
name: httpbin-gateway
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
$ kubectl apply -f - <<EOF
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: "frontend-ingress"
namespace: foo
spec:
targetRef:
kind: Gateway
group: gateway.networking.k8s.io
name: httpbin-gateway
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
$ kubectl apply -f - <<EOF
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: "frontend-ingress"
namespace: foo
spec:
targetRef:
kind: Gateway
group: gateway.networking.k8s.io
name: httpbin-gateway
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
認証ポリシーの削除
$ kubectl -n istio-system delete requestauthentication jwt-example
承認ポリシーの削除
$ kubectl -n istio-system delete authorizationpolicy frontend-ingress
トークンジェネレータースクリプトとキーファイルの削除
$ rm -f ./gen-jwt.py ./key.pem
後続のタスクを実行しない場合は、テスト名前空間を削除するだけで、すべてのリソースを削除できます。
$ kubectl delete ns foo bar legacy