不正アクセス、盗聴を防ぐ――マイクロサービスのセキュリティを向上させる4つのIstio機能Cloud Nativeチートシート(12)

Kubernetesやクラウドネイティブをより便利に利用する技術やツールの概要、使い方を凝縮して紹介する連載。今回は、Istioのセキュリティ機能に焦点を当て、通信の暗号化、認証/認可の機能を紹介する。

» 2022年01月21日 05時00分 公開
[毛利健太郎, 岡本隆史株式会社NTTデータ]

この記事は会員限定です。会員登録(無料)すると全てご覧いただけます。

 Kubernetesやクラウドネイティブをより便利に利用する技術やツールの概要、使い方を凝縮して紹介する本連載「Cloud Nativeチートシート」。前回前々回とサービスメッシュ、特にIstioの機能について説明してきましたが、今回も引き続きIstioの機能について紹介していきます。今回はIstioのセキュリティ機能に焦点を当て、通信の暗号化、認証/認可の機能を紹介します。

マイクロサービス環境に潜むセキュリティリスク

 マイクロサービスアーキテクチャでは、アジリティの向上、スケーラビリティの向上、サービスの再利用が可能になるなど、さまざまなメリットが得られます。しかし、複数のサービスが連携して1つのシステムを構成する特徴から、モノリシックアーキテクチャでは考慮する必要のなかった点が課題となります。

 例として、以下のようなリスクが存在します。

不正アクセスのリスク

 モノリシックなアプリケーションでは、サービスは1つのネットワークにあるサーバで動作するので、公開されたエンドポイントに着目したり、サーバのIPアドレスを特定することが可能であったりと、セキュリティ対策が比較的容易でした。

 一方、マイクロサービスでは、サービスが頻繁かつ動的に分散配置されるので、割り当てられるIPアドレスが短期間で変化します。また、サービス間で通信し、サービス単位にエンドポイントを用意するので、不正アクセスされるリスクが高まります。

不正アクセスのリスク

盗聴のリスク

 モノリシックなサービスの場合、プロセス間はメモリを用いて通信することが一般的でした。そのため、公開されたエンドポイントとの通信を暗号化することで一定のセキュリティレベルを保証できます。

 一方マイクロサービスの場合、ネットワーク経由でサービス同士が通信し合うことになります。そのため、仮にマイクロサービス間の通信が暗号化されていない場合、第三者から通信を傍受されるリスクが存在します。

盗聴のリスク

 上記で挙げたセキュティリスクは一部にすぎませんが、確実に対策する必要があります。下表の対策方法が一般的でしょう。

脅威(被害) セキュリティ対策
不正アクセス(情報資産の改ざん/変更、情報資産の破壊/削除、情報資産の流出) 各マイクロサービス間での認証
外部アクセスユーザーの認証
サービス/ユーザーの認可(アクセス権の設定)
盗聴(情報資産/個人情報の流出) 外部からの通信の暗号化
サービス間通信の暗号化

 これらの最も単純な実現方法には、各サービスを構成するアプリケーションに、サービス間通信を規定するロジックを実装することが考えられます。しかし、全てのサービスで該当ロジックを維持することは困難なので、このアプリケーション層で全て吸収する方法はあまり現実的ではありません。

 そこで本記事では、Istioの機能を活用し、マイクロサービスの課題を解決する方法を解説します。「Istioがどのような仕組みでセキュリティ機能を構成するのか」「どのリソースを使うことで、上記のセキュリティ課題への対策が可能になるのか」を確認します。

Istioの4つのセキュリティ機能

 Istioのセキュリティ機能は主に4つです。

 各セキュリティ機能は、これまで紹介したリソースと同様にコントロールプレーンの「Istiod」コンポーネントをからデータプレーンに対して関連する設定を伝達、管理するだけで稼働します。つまり、今回もアプリケーションのソースコードを変更する必要はありません。

 以下、4つのセキュリティ機能の概要をします。

1.TLS終端

 前述の不正アクセスリスクの対策としては認証機能の導入、盗聴へのリスクに対しては暗号化された通信があります。「Ingress Gateway」はTLSに対応しており、クラスタ外からクラスタ内への通信を暗号化し、終端することが可能です。証明書と鍵をIngress Gatewayにマウントすることで簡単に実装できます。

 この機能は、外部にサービスを公開する場合など最も使用される機能といえるでしょう。

TLS終端

 なおTLS終端には、「Gateway」リソースが使用されます。

2.相互TLS認証

 Istioでは、X.509証明書を利用した相互TLS認証(mTLS)が提供されており、マイクロサービス間の認証と通信の暗号化も実現可能です。

 「内部ネットワークなのに相互認証させる必要があるのか?」と思う方もいると思います。確かに内部通信まで認証するのは過剰なケースもあるでしょう。しかし、機密性の高い顧客データをマイクロサービス間で送信する場合はどうでしょうか。ネットワークにアクセスできる人は誰でもこの機密データを読み取って要求を偽造できる可能性があります。特に各サービスの開発チームが異なる場合は、内部脅威も考慮しなければなりません。このようなケースには相互TLS認証で脅威を最小限にします。

 なお具体的な仕組みとして、IstioのIDと証明書の管理は、「SPIFFE(Secure Production Identity Framework For Everyone)」仕様を使います。SPIFFEの詳細については本記事では割愛しますが、Istioでは「X.509」証明書形式の「SVID」(SPIFFE Verifiable Identity Document)を用いることで、各サービス間の通信においてサービス相手の正当性を検証します。

 仕組みは通常のPKI(Public Key Infrastructure)と同様です。まず、デフォルトではCA(Certification Authority:認証局)を管理するIstiodで証明書を発行し、「Envoy」に配布します。通信時にはこの証明書を互いに送信し、CAの公開鍵でSVIDを検証することでサービス相手の正当性を確認できます。また、これらの証明書の発行とローテーションはIstiodで自動化されているので、不要な運用コストを削減することもできます。

相互TLS認証

 この相互TLS認証を設定するリソースとしては、「PeerAuthentication」が使用されます。

3.JWT検証

 相互TLS認証によるマイクロサービス間の認証に加えて、JWT検証によるリクエストレベルの認証が実装されており、認証トークンを検証するエンドユーザー認証が可能です。「Auth0」「Keycloak」といった認証プロバイダーなどを使えるので、独自に作り込むことなく認証機能を実現できます。

 なお、JWT検証は個別のマイクロサービスにも適用できますが、多くの場合はIngress Gatewayに適用され、設定するリソースとしては、「RequestAuthentication」が使用されます。

JWT検証

4.アクセス制御機能(認可機能)

 ここでの「アクセス制御」とは、リクエスト元のサービスのアクセス権限を確認した上でリクエストを「許可/拒否する」認可処理のことを指します。Istioは、相互認証やJWT検証などの認証機能と組み合わせて、各主体に対しての認可を設定する機能を持っています。具体的には「AuthorizationPolicy」を用いて、認証された主体が各サービスへのアクセスの可否などの操作を定義できます。

アクセス制御機能

Istioのセキュリティの全体像まとめ(セキュリティ機能とリソースの関係)

 ここまでIstioの4つのセキュリティ機能の概要を確認しました。各機能をマイクロサービスに適用することで、セキュリティリスクを低減可能です。

 また、それぞれの機能を使用するには、Istioのリソースを利用する必要がありました。これ以降、これらのリソースを用いてサンプルの「Bookinfo」アプリケーションにセキュリティ機能を適用しますが、リソースの関係が分からなくなった場合には、こちらの表を見て関係性を再度把握してみてください。

脅威(被害) セキュリティ対策 Istioの機能 Istioのリソース
不正アクセス(情報資産の改ざん/変更、情報資産の破壊/削除、情報資産の流出) 各マイクロサービス間での認証 相互TLS認証 PeerAuthentication
外部アクセスユーザーの認証 JWT検証 RequestAuthentication
サービス/ユーザーの認可(アクセス権の設定) アクセス制御機能(認可機能) AuthorizationPolicy
盗聴(情報資産/個人情報の流出) 外部からの通信の暗号化 TLS終端 Gateway
サービス間通信の暗号化 相互TLS認証 PeerAuthentication

事前準備:サンプルアプリケーションのデプロイ

 今回は、下記の環境で環境を構築しました。本稿でもインストール手順を記載しますが、詳細なインストール方法を知りたい方は、連載第10回を参考にしてください。

対象 説明 動作確認で利用した環境
Kubernetes Azure Kubernetes Service、Google Kubernetes Engineなどのクラウドのマネージドサービス環境や「minikube」などのローカル環境おけるKubernetesクラスタ Azure Kubernetes Service(v1.21)
Istio サービスメッシュソフトウェア v1.11.3

 セキュリティ機能を適用する前に、まずはBookinfoアプリケーションを使用して通信の状態を見てみましょう。事前確認として平文でのサービス間の通信が可能かどうかを確認します。

 なお、サービスメッシュ内との通信とサービスメッシュ外の通信の両方を比較するために「Sleep」アプリケーションもデプロイしています。

  • Bookinfoアプリケーションをnamespace:sample-appにデプロイ
  • Sleepアプリケーションをnamespace:sample-appにデプロイ(サイドカープロキシあり)
  • Sleepアプリケーションをnamespace:defaultにデプロイ(サイドカープロキシなし)

検証環境
# Istioダウンロード:最新版を使用する場合(今回は1.11.3を使用)
$ curl -L https://istio.io/downloadIstio | sh -
 
# 事前にコマンドのパスを通しておく
$ cd ./istio-1.11.3
$ export PATH=$PWD/bin:$PATH
 
# Istioのインストール
$ istioctl install --set profile=demo -y
 
# 対象のnamespaceでサイドカープロキシの自動挿入が有効ではない場合は、有効にしてください
$ kubectl create namespace sample-app
$ kubectl label namespace sample-app istio-injection=enabled
 
# アプリケーションのデプロイ
$ kubectl apply -f ./samples/bookinfo/platform/kube/bookinfo.yaml -n sample-app
$ kubectl apply -f ./samples/sleep/sleep.yaml -n sample-app
$ kubectl apply -f ./samples/sleep/sleep.yaml -n default
 
# 反映確認
$ kubectl get pod -n sample-app                                                                                                                                    
NAME                              READY   STATUS    RESTARTS   AGE
details-v1-79f774bdb9-l2mx2       2/2     Running   0          6h1m
productpage-v1-6b746f74dc-ht2t2   2/2     Running   0          126m
ratings-v1-b6994bb9-p7gpz         2/2     Running   0          6h1m
reviews-v1-545db77b95-4s8zw       2/2     Running   0          5h56m
reviews-v2-7bf8c9648f-nnxw2       2/2     Running   0          6h1m
reviews-v3-84779c7bbc-kbdzs       2/2     Running   0          6h1m
sleep-557747455f-mmkk7            2/2     Running   0          30s
 
$ kubectl get pod
NAME                     READY   STATUS    RESTARTS   AGE
sleep-557747455f-xtl8p   1/1     Running   0          28s

 ここまでで事前準備は完了です。以降で前述の4つのセキュリティ機能単位にリソースの使用方法や具体的な挙動を確認します。

セキュリティ機能の実践【1】Ingress GatewayでのTLS終端

 外部からのアクセスをTLS通信とし、Ingress Gatewayで終端させる設定を試します。今回は、自己証明書を使用したサーバ認証を設定します。ホスト「bookinfo.istio-sample.com」に対して自己証明書を作成する手順を紹介します。

# 自己証明書の作成
$ openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -subj '/O=bookinfo/CN=Istio-sample.com' -keyout istio-sample.com.key -out istio-sample.com.crt
$ openssl req -out bookinfo.istio-sample.com.csr -newkey rsa:2048 -nodes -keyout bookinfo.istio-sample.com.key -subj "/CN=bookinfo.istio-sample.com/O=bookinfo.istio-sample"
$ openssl x509 -req -days 365 -CA istio-sample.com.crt -CAkey istio-sample.com.key -set_serial 0 -in bookinfo.istio-sample.com.csr -out bookinfo.istio-sample.com.crt

 証明書が作成できたら、秘密鍵と証明書を「Secret」リソースとして登録します。

# シークレットリソースの作成
kubectl create -n istio-system secret tls bookinfo-credential --key=bookinfo.istio-sample.com.key --cert=bookinfo.istio-sample.com.crt

 「Ingress Gatewayで終端させる」と先述しましたが、その設定はGatewayで行います。HTTPSで「bookinfo.istio-sample.com」に対する通信を受け入れます。「tls.mode」の「SIMPLE」が通常のTLSモードです。証明書はSecretの名前で指定します。

apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: bookinfo-gateway
  namespace: sample-app
spec:
  selector:
    istio: ingressgateway
  servers:
  - port:
      number: 443
      name: https
      protocol: HTTPS
    tls:
      mode: SIMPLE
      credentialName: bookinfo-credential #先ほど作成したSecretを指定
    hosts:
    - bookinfo.istio-sample.com
tls_termination.yaml

 また、Gatewayと各アプリケーションをVirtual Serviceでひも付ける必要があります。「hosts」にbookinfo.istio-sample.comを追加します。

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: bookinfo
  namespace: sample-app
spec:
  hosts:
  - "bookinfo.istio-sample.com"
  gateways:
  - bookinfo-gateway
  http:
  - match:
    - uri:
        exact: /productpage
    - uri:
        prefix: /static
    - uri:
        exact: /login
    - uri:
        exact: /logout
    - uri:
        prefix: /api/v1/products
    route:
    - destination:
        host: productpage
        port:
          number: 9080
virtualservice_tls.yaml

 準備ができたら、リソースを反映します。

# 設定の反映
$ kubectl apply -f ./tls_termination.yaml
$ kubectl apply -f ./virtualservice_tls.yaml
# リソースの確認
$ kubectl get gateway -n sample-app
NAME               AGE
bookinfo-gateway   12s
 
$ kubectl get virtualservice -n sample-app
NAME       GATEWAYS               HOSTS   AGE
bookinfo   ["bookinfo-gateway"]   ["*"]   24s

 設定が完了しました。最後にHTTPSでアクセスできることを確認します。

# 環境変数の設定
$ export INGRESS_HOST=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
$ export INGRESS_PORT=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.spec.ports[?(@.name=="http2")].port}')
 
# HTTPSでのアクセス  
# -Hでホストヘッダを書き換える
# --resolveで一時的な名前解決を実施
# --cacertで作成した証明書を指定し、ルート証明書として利用させる
$ curl -HHost:bookinfo.istio-sample.com --resolve "bookinfo.istio-sample.com:$SECURE_INGRESS_PORT:$INGRESS_HOST" -o /dev/null -w '%{http_code}\n' -s --cacert istio-sample.com.crt "https://bookinfo.istio-sample.com:$SECURE_INGRESS_PORT/productpage"

 以上でIngress GatewayでTLSを終端させる設定は完了です。他のセキュリティ機能はあまり使用しない場合でもIngress Gatewayでサーバ証明書を使うケースは多いでしょう。今回は基本となる手順を紹介しましたが、より実践的な手順に関しては、Istio公式ページ(Secure Gateways)を参考にしてください。

セキュリティ機能の実践【2】相互TLS認証機能

 本機能はリソース、namespace単位など柔軟に設定できるので、環境や要件に応じた使い方ができます。今回は、下表に示す流れで基本的な設定内容と設定後の通信の違いを確認します。

項番 設定/検証内容 確認内容
1 デフォルト状態 サービスメッシュ内外からの通信が可能であること
2 相互TLS認証設定(STRICT:namespace全体) サービスメッシュ外からの平文の通信が失敗すること
3 相互TLS認証設定(PERMISSIVE:productpageのみ) 項番2の設定を上書きし、サービスメッシュ内外から特定のサービス(productpage)への通信が可能であること
4 Destination Ruleを組み合わせた相互TLS認証 サイドカープロキシが存在しないサービスが混在する状況で相互TLS認証での通信が可能であること

1.デフォルト状態

 サービス間の通信を暗号化する相互TLS認証について確認します。最初に相互TLS認証を設定していない場合の動作を確認します。2つのSleepアプリケーションからproductpageにHTTPリクエストを発行します。通信が正常に行われた場合は、ステータスコード「200」を返すはずです。

# サービスメッシュ内のPodからproductpageにアクセス
$ echo 'from "mesh" to productpage' && kubectl exec -n sample-app "$(kubectl get pod -n sample-app -l app=sleep -o jsonpath={.items..metadata.name})" -- curl http://productpage.sample-app:9080/productpage -o /dev/null -w '%{http_code}\n' -s
from "mesh" to productgpege
200
 
# サービスメッシュ外のPodからproductpageにアクセス
$ echo 'from "outside mesh" to productpage' && kubectl exec "$(kubectl get pod -l app=sleep -o jsonpath={.items..metadata.name})" -- curl http://productpage.sample-app:9080/productpage -o /dev/null -w '%{http_code}\n' -s
from "outside mesh" to productpage
200

相互TLS認証設定のモード

 ここから相互TLS認証設定を実施します。相互TLS認証設定はPeerAuthenticationリソースで設定できます。対象範囲は個別のサービス、namespace、サービスメッシュ全体と用途に応じて設定が可能です。さらに認証モードの指定をすることで通信間の認証の要否を制御できます。

 認証モードと認証の要否はこのように整理できます。

相互TLS設定(モード) メッシュ内からの通信 メッシュ外からの通信
PERMISSIVE 相互TLS通信(暗号化通信) 非暗号化通信
STRICT 相互TLS通信(暗号化通信) 通信不可
DISABLED 非暗号化通信 非暗号化通信

 PeerAuthenticationリソースを設定していない状態では、サービスメッシュ全体が「PERMISSIVE」モードで動作します。そのため、今回の環境では、サービスメッシュ内の「namespace:sample-app」のアプリケーションから通信する場合は、相互TLS認証が使用されます。一方、サービスメッシュ外の環境においては認証なしで通信します。

2.相互TLS認証設定(STRICT:namespace全体)

 ここではPeerAuthenticationリソースを使用して、相互TLS認証の範囲とモードを指定します。現在はサービスメッシュ全体がPERMISSIVEモードなので、「namespace:sample-appには相互TLS認証が必須(STRICTモード)となる」設定を入れます。

・PeerAuthenticationの設定

apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
  name: mtls
  namespace: sample-app  # 対象範囲をnamespace全体にする
spec:
  mtls:
    mode: STRICT      # mtlsを必須とさせる設定
peer-authentication.yaml

 準備ができたので、マニフェストファイルをnamespace:sample-appに適用します。

# PeerAuthenticationを適用
$ kubectl apply -f ./peer-authentication.yaml
peerauthentication.security.istio.io/mtls created
 
# namespace: sample-appにSTRICTモードのリソースが存在することを確認
$ kubectl get peerauthentications.security.istio.io -n sample-app
NAME   MODE     AGE
mtls   STRICT   30s

・通信の確認

 期待される動作は、次の通りです。

  • サービスメッシュ外(namespace:default)からproductpageの通信:通信失敗(相互TLS認証ができないため)
  • サービスメッシュ内(namespace:sample-app)からproductpageの通信:通信可能(相互TLS認証、暗号化通信)

namespace全体のSTRICTモードでの動作
# サービスメッシュ外(namespace:default)からproductpageの通信
$ echo 'from "outside mesh" to productpage' && kubectl exec "$(kubectl get pod -l app=sleep -o jsonpath={.items..metadata.name})" -- curl http://productpage.sample-app:9080/productpage -o /dev/null -w '%{http_code}\n' -s
from "outside mesh" to productgpege
000
command terminated with exit code 56
 
# サービスメッシュ内(namespace:sample-app)からproductpageの通信
$ echo 'from "mesh" to productpage' && kubectl exec -n sample-app "$(kubectl get pod -n sample-app -l app=sleep -o jsonpath={.items..metadata.name})" -- curl http://productpage.sample-app:9080/productpage -o /dev/null -w '%{http_code}\n' -s
from "mesh" to productpage
200

 先ほどまで通信できていた、サービスメッシュ外(サイドカープロキシのないPod)からの通信が失敗することを確認できます。これは、相互TLS認証時にクライアント認証ができず、通信を確立できないからです。

3.相互TLS認証設定(PERMISSIVE:productpageのみ)

 先ほどの設定ではnamespace全体に相互TLS認証を強制していました。サービス間の通信をTLS相互認証によって暗号化していますが、前段のIngress Gatewayで暗号化通信を一度終端しており、ユーザーがアクセスするWebページには、サービスの認証は必要としません。ここでは、Webページを表示するproductpageの認証を外した場合を想定してみます。

・PeerAuthenticationの設定

apiVersion: "security.istio.io/v1beta1"
kind: "PeerAuthentication"
metadata:
  name: productpage
  namespace: sample-app
spec:
  selector:
    matchLabels:
      app: productpage    # 対象をproductpageとする
  mtls:
    mode: PERMISSIVE     # 相互TLS認証ができない場合は必須としない
# PeerAuthenticationを適用
kubectl apply -f ./peer-authentication_productpage.yaml
peerauthentication.security.istio.io/productpage created
# 作成したリソースが存在することを確認(PERMISSIVEモード)
kubectl get peerauthentications.security.istio.io -n sample-app
NAME          MODE         AGE
mtls          STRICT       11m
productpage   PERMISSIVE   7s

productpage:PERMISSIVEモードでの動作

・通信の確認

 期待される動作は、次の通りです。

  • サービスメッシュ外(namespace:default)からproductpageの通信:通信可
  • サービスメッシュ内(namespace:sample-app)からproductpageの通信:通信可能(相互TLS認証、暗号化通信)
# サービスメッシュ外(namespace:default)からproductpageの通信
echo "from no sidecar to productpage" && kubectl exec "$(kubectl get pod -l app=sleep -o jsonpath={.items..metadata.name})" -- curl http://productpage.sample-app:9080/productpage -o /dev/null -w '%{http_code}\n' -s
from no sidecar to productpage
200
 
# サービスメッシュ内(namespace:sample-app)からproductpageの通信
echo 'from "mesh" to productpage' && kubectl exec -n sample-app "$(kubectl get pod -n sample-app -l app=sleep -o jsonpath={.items..metadata.name})" -- curl http://productpage.sample-app:9080/productpage -o /dev/null -w '%{http_code}\n' -s
from "mesh" to productpage
200

 先ほど通信に失敗したサービスメッシュ外(サイドカープロキシのないPod)からの通信が成功することを確認できます。この設定は、「サービスメッシュ全体に相互TLS認証を実施したいが、サービスメッシュ外からのアクセスも存在する」といった要件にも柔軟に対応できるでしょう。

4.Destination Ruleを組み合わせた相互TLS認証

 ここまでは、PeerAuthenticationのみを使用して相互TLS認証の要否を設定してきました。先ほどまでのケースでは、相互TLS認証が実施できない場合はエラーを表示しましたが、もっと柔軟に設定したい場合があるかもしれません。

 複数バージョンを扱うサービスにおいて、一部のバージョンではサイドカープロキシが存在しないケースを考えてみます。セキュリティ上、常に相互TLS通信を適用させたくても、サイドカープロキシが存在しないので、PeerAuthenticationだけでは制御できません。

Destination Ruleを使用するケース

 上記のように相互TLS認証を使用した通信を規定する場合、Destination Ruleを組み合わせることでより柔軟に対処できます。下表に示す流れでDestination Ruleの設定前と設定後の動作の違いを確認します。

項番 設定/検証内容 確認内容
1 PeerAuthenticationをnamespace適用(STRICTモード) サイドカープロキシが存在しないサービスにルーティングされた場合において、サーバ認証に失敗し、エラーページが表示されること
2 Destination Ruleの適用 サイドカープロキシが存在しないサービスにルーティングされなくなり、相互TLS認証での通信に限定されること

・reviewsサービスでの事前確認

 まずは次の状態を作成します。

  • reviews v2のみサイドカープロキシが存在しない状態
  • PeerAuthentication:PERMISSIVEモード(平文でも通信可能)
# PeerAuthenticationリソースの削除
$ kubectl delete peerauthentications.security.istio.io -n sample-app mtls
$ kubectl delete peerauthentications.security.istio.io -n sample-app productpage
 
# 削除されたことの確認(デフォルトはPERMISSIVEモード)
kubectl get peerauthentications.security.istio.io -n sample-app
No resources found in sample-app namespace.
 
# reviews v2の削除
$ kubectl delete deployment -n sample-app reviews-v2
deployment.apps "reviews-v2" deleted
 
# サイドカーの自動挿入設定を停止
$ kubectl label namespace sample-app istio-injection=disabled --overwrite
namespace/sample-app labeled
 
# Bookinfoアプリケーションのデプロイ
$ kubectl apply -f ./samples/bookinfo/platform/kube/bookinfo.yaml -n sample-app
service/details unchanged
serviceaccount/bookinfo-details unchanged
deployment.apps/details-v1 unchanged
service/ratings unchanged
serviceaccount/bookinfo-ratings unchanged
deployment.apps/ratings-v1 unchanged
service/reviews unchanged
serviceaccount/bookinfo-reviews unchanged
deployment.apps/reviews-v1 unchanged
deployment.apps/reviews-v2 created
deployment.apps/reviews-v3 unchanged
service/productpage unchanged
serviceaccount/bookinfo-productpage unchanged
deployment.apps/productpage-v1 unchanged
 
# reviews-v2のREADY欄が1であり、サイドカーが存在しないことを確認
$ kubectl get po -n sample-app
NAME                              READY   STATUS    RESTARTS   AGE
details-v1-79f774bdb9-vssjg       2/2     Running   0          3h42m
productpage-v1-6b746f74dc-xsptl   2/2     Running   0          3h42m
ratings-v1-b6994bb9-4vc4l         2/2     Running   0          3h42m
reviews-v1-545db77b95-7wrpz       2/2     Running   0          3h42m
reviews-v2-7bf8c9648f-c4wmf       1/1     Running   0          9s
reviews-v3-84779c7bbc-574jt       2/2     Running   0          3h42m
sleep-557747455f-9vs6v            2/2     Running   0          3h42m

・通信の確認

 ここまで準備できたら、ブラウザからBookinfoアプリケーションにアクセスします。設定内容についての詳細を知りたい方は、連載第10回を参考にしてください。

$ kubectl apply -f ./samples/bookinfo/networking/bookinfo-gateway.yaml -n sample-app
 
# リソースの確認
$ kubectl get gateway -n sample-app
NAME               AGE
bookinfo-gateway   12s
 
$ kubectl get virtualservice -n sample-app
NAME       GATEWAYS               HOSTS   AGE
bookinfo   ["bookinfo-gateway"]   ["*"]   24s
# type: LoadBalancerを使用する場合は以下の環境変数を設定
$ export INGRESS_HOST=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
$ export INGRESS_PORT=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.spec.ports[?(@.name=="http2")].port}')
 
# クラスタの外部からBookinfoアプリケーションにアクセス
$ curl -s "http://${INGRESS_HOST}:${INGRESS_PORT}/productpage" | grep -o "<title>.*</title>"
 
# ブラウザからアクセスするためにURLを表示
$ echo http://${INGRESS_HOST}:${INGRESS_PORT}/productpage

 productpage経由でreviewsに通信を発生させるとv2を含めたサービスにも負荷分散され、正常にページが表示されることを確認できます。これは、現在の設定がデフォルトのPERMISSIVEモードなので、相互TLS認証を使用しない通信も許可されるからです。

4-1.PeerAuthenticationをnamespace適用(STRICT)

 PeerAuthenticationリソースで、namespace全体をSTRICTモードにした場合はどうでしょうか。先ほど作成したマニフェストを再度適用します。

・PeerAuthenticationの設定

# PeerAuthenticationを適用
$ kubectl apply -f ./peer-authentication.yaml
peerauthentication.security.istio.io/mtls created
 
# namespace: sample-appにSTRICTモードのリソースが存在することを確認
$ kubectl get peerauthentications.security.istio.io -n sample-app
NAME   MODE     AGE
mtls   STRICT   40s

・通信の確認

 先ほどと同様にブラウザからproductpageにアクセスすると、reviews-v2に通信が負荷分散された際はエラーページが表示されます。productpageからreviewsサービスに通信する際に相互TLS認証を必須としているにもかかわらず、reviews-v2ではサイドカープロキシが存在しないので、reviewsサービスのサーバを認証できず、通信が失敗します。

reviewsのサーバ認証の失敗

reviewsのサーバ認証の失敗2

4-2.Destination Ruleの適用

 このような環境でもDestination Ruleを組み合わせることでエラーページを表示させないようにすることができます。つまり、Destination Ruleで通信ルールを規定することで、相互TLS認証が可能なサービスのみにルーティングさせることができるので、サイドカーのないreviewsサービスには通信が発生しないようになります。

・Destination Ruleの設定

 下記のようにtrafficPolicyを規定することでreviewsサービスへの通信には相互TLS認証を使用することを明示できます。

apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: reviews
spec:
  host: reviews
  trafficPolicy:
    tls:
      mode: ISTIO_MUTUAL   # mTLSを使用するルールを規定する

 マニフェストを適用し、通信を発生させます。

# mTLSを使用するように設定したDestination Ruleの適用
$ kubectl apply destinationRule_reviews.yaml
destinationrule.networking.istio.io/reviews created
 
$ kubectl get destinationrules.networking.istio.io -n sample-app
NAME      HOST      AGE
reviews   reviews   27s

・通信の確認

 ブラウザからアクセスするとv2が表示されないはずです。念のため、下記コマンドで10回ほどproductpageからreviewsサービスに対しての通信を発生させてみます。

# productpageからreviewsサービスへの通信
for i in `seq 10` ; do ; ret=`kubectl exec "$(kubectl get pod -n sample-app -l app=sleep -o jsonpath={.items..metadata.name})" -n sample-app -- curl -s http://productpage.sample-app:9080/productpage | grep 'font color=' | tail -n1 | awk '{print $2}'` ; if [ "`echo $ret | grep red`" ] ; then ; echo v3 ; elif [ "`echo $ret | grep black`" ] ; then ; echo v2 ; else echo v1 ; fi ; done
v1
v3
v1
v1
v1
v3
v1
v1
v3
v1

 同じくv2は表示されません。つまり、Destination Ruleで通信ルールが規定されるので、TLS通信に限定されることが分かります。

 このようにPeerAuthenticationとDestination Ruleを組み合わせることで柔軟に相互TLS認証を規定できるので、セキュリティ要件や環境に応じて適用してみるといいでしょう。

 本記事では紹介しませんが、その他、ポートごとに相互TLS認証を設定することもできます。詳細については、Istio公式ページ(Authentication Policy)を確認してください。

コラム 相互TLS認証設定時のパケットキャプチャー

 Istioの相互TLS機能でサービス間通信が暗号化され、第三者からの盗聴を防ぐことができると説明しましたが、パケットはどのようになっているのでしょうか。設定前後において、受信側であるproductpageのサイドカープロキシにパケットキャプチャーを設定し、内容を確認します。

事前準備

 パケットキャプチャーを取得するために、サイドカープロキシを特権コンテナとして利用できるようにインストールします。この設定は、コンテナがホストであるサーバへの高度なアクセス権限を持ち、攻撃者に悪用されるリスクを伴うので、検証やデバッグの際にのみ使用するようにしてください。

# Istioのインストール時に特権コンテナを有効にする(privileged=true)
$ istioctl install --set profile=demo --set values.global.proxy.privileged=true -y
 
# 環境変数として、Pod名とIPアドレスをセットする
$ productpage=`kubectl get pod -n sample-app -l app=productpage -o jsonpath='{.items[0].metadata.name}'`
$ podIP=`kubectl get pod -n sample-app -l app=productpage -o jsonpath='{.items[0].status.podIP}'`
 
# サイドカープロキシでパケットキャプチャーを取得
$ kubectl exec -n sample-app $productpage -c istio-proxy -- sudo tcpdump -vvvv -A -i eth0 dst port 9080 and net $podIP

デフォルト(PERMISSIVE)の場合

 パケットキャプチャーの準備ができたら、通信を発生させます。

# デフォルト(PERMISSIVE)での確認
kubectl get peerauthentications.security.istio.io -n sample-app                                                                                                                    
No resources found in sample-app namespace.
 
# サービスメッシュ内のPodからproductpageにアクセス
$ echo 'from "mesh" to productpage' && kubectl exec -n sample-app "$(kubectl get pod -n sample-app -l app=sleep -o jsonpath={.items..metadata.name})" -- curl http://productpage.sample-app:9080/productpage -o /dev/null -w '%{http_code}\n' -s
from "mesh" to productpage
200
 
# サービスメッシュ外のPodからproductpageにアクセス
$ echo 'from "outside mesh" to productpage' && kubectl exec "$(kubectl get pod -l app=sleep -o jsonpath={.items..metadata.name})" -- curl http://productpage.sample-app:9080/productpage -o /dev/null -w '%{http_code}\n' -s
from "outside mesh" to productpage
200

 パケットキャプチャーの内容を確認します。サービスメッシュ外からアクセスした場合(サイドカープロキシがない場合)は平文で通信されており、サービスメッシュ内からアクセスした場合(サイドカープロキシがある場合)の通信は暗号化されていることを確認できます。

デフォルト(PERMISSIVE)でのパケットキャプチャー

# パケットキャプチャーの内容(サービスメッシュ内のPodからproductpageにアクセス(サイドカーあり))
09:18:28.708284 IP (tos 0x0, ttl 63, id 32182, offset 0, flags [DF], proto TCP (6), length 270)
    172-16-4-65.sleep.sample-app.svc.cluster.local.45012 > productpage-v1-6b746f74dc-xsptl.9080: Flags [P.], cksum 0x6198 (incorrect -> 0xdd07), seq 0:218, ack 1, win 502, options [nop,nop,TS val 1182729210 ecr 789293290], length 218
E...}.@.?.\....A...6..#x....T.......a......
F.../..............4.......T ...G.....p<.RnB.#z.,|=....+.../...,.0.......?.=..:outbound_.9080_._.productpage.sample-app.svc.cluster.local..........
...............#..... ...istio-http/1.1.istio.http/1.1........................

DISABLEの場合

 相互TLS認証を使用しない設定も確認します。先ほどのマニフェストファイルのモード設定をDISABLEとするだけです。

apiVersion: "security.istio.io/v1beta1"
kind: "PeerAuthentication"
metadata:
  name: productpage
  namespace: sample-app
spec:
  selector:
    matchLabels:
      app: productpage   # 対象は、productpage
  mtls:
    mode: DISABLE      # 相互TLS認証を使用しない

 リソースをデプロイし、通信を発生させます。

# PeerAuthenticationを適用
kubectl apply -f ./peer-authentication_productpage.yaml
peerauthentication.security.istio.io/productpage created
 
# 作成したリソースが存在することを確認(PERMISSIVEモード)
kubectl get peerauthentications.security.istio.io -n sample-app
NAME          MODE         AGE
mtls          STRICT       12m
productpage   DISABLE      10s
 
# サービスメッシュ内(namespace:sample-app)からproductpageの通信
echo 'from "mesh" to productpage' && kubectl exec -n sample-app "$(kubectl get pod -n sample-app -l app=sleep -o jsonpath={.items..metadata.name})" -- curl http://productpage.sample-app:9080/productpage -o /dev/null -w '%{http_code}\n' -s
from "mesh" to productpage
200

 パケットキャプチャーの内容を確認します。

# サービスメッシュ内(namespace:sample-app)からproductpageの通信のパケットキャプチャー(DISABLEDモード)
06:57:07.241136 IP (tos 0x0, ttl 63, id 64011, offset 0, flags [DF], proto TCP (6), length 1658)
    172-16-4-65.sleep.sample-app.svc.cluster.local.42414 > productpage-v1-6b746f74dc-xsptl.9080: Flags [P.], cksum 0x6704 (incorrect -> 0xeefa), seq 0:1606, ack 1, win 502, options [nop,nop,TS val 1174247743 ecr 780811823], length 1606
E..z..@.?......A...6..#x.9|.........g......
E..?..>/GET /productpage HTTP/1.1
host: productpage.sample-app:9080
user-agent: curl/7.79.1-DEV
accept: */*
x-forwarded-proto: http
x-request-id: f88d7e4d-c822-94e8-a84d-9d6fd70f6720
x-envoy-decorator-operation: productpage.sample-app.svc.cluster.local:9080/*
x-envoy-peer-metadata: ChkKDkFQUF9DT05UQUlORVJTEgcaBXNsZWVwChoKCkNMVVNURVJfSUQSDBoKS3ViZXJuZXRlcwoZCg1JU1RJT19WRVJTSU9OEggaBjEuMTEuMArEAQoGTEFCRUxTErkBKrYBCg4KA2FwcBIHGgVzbGVlcAohChFwb2QtdGVtcGxhdGUtaGFzaBIMGgo1NTc3NDc0NTVmCiQKGXNlY3VyaXR5LmlzdGlvLmlvL3Rsc01vZGUSBxoFaXN0aW8KKgofc2VydmljZS5pc3Rpby5pby9jYW5vbmljYWwtbmFtZRIHGgVzbGVlcAovCiNzZXJ2aWNlLmlzdGlvLmlvL2Nhbm9uaWNhbC1yZXZpc2lvbhIIGgZsYXRlc3QKGgoHTUVTSF9JRBIPGg1jbHVzdGVyLmxvY2FsCiAKBE5BTUUSGBoWc2xlZXAtNTU3NzQ3NDU1Zi05dnM2dgoZCglOQU1FU1BBQ0USDBoKc2FtcGxlLWFwcApMCgVPV05FUhJDGkFrdWJlcm5ldGVzOi8vYXBpcy9hcHBzL3YxL25hbWVzcGFjZXMvc2FtcGxlLWFwcC9kZXBsb3ltZW50cy9zbGVlcArSAgoRUExBVEZPUk1fTUVUQURBVEESvAIquQIKQAoUYXp1cmVfY3JlYXRpb25Tb3VyY2USKBomdm1zc2NsaWVudC1ha3MtYWdlbnRwb29sLTEwODU1Nzg1LXZtc3MKHQoOYXp1cmVfbG9jYXRpb24SCxoJamFwYW5lYXN0Ci4KCmF6dXJlX25hbWUSIBoeYWtzLWFnZW50cG9vbC0xMDg1NTc4NS12bXNzXzY5CikKEmF6dXJlX29yY2hlc3RyYXRvchITGhFLdWJlcm5ldGVzOjEuMjEuMQodCg5henVyZV9wb29sTmFtZRILGglhZ2VudHBvb2wKJgoYYXp1cmVfcmVzb3VyY2VOYW1lU3VmZml4EgoaCDEwODU1Nzg1CjQKCmF6dXJlX3ZtSWQSJhokOWU3MTllMDAtZGNiOC00OGJmLTg1ZmEtNWE2MDVmN2RlZGJiChgKDVdPUktMT0FEX05BTUUSBxoFc2xlZXA=
x-envoy-peer-metadata-id: sidecar~172.16.4.65~sleep-557747455f-9vs6v.sample-app~sample-app.svc.cluster.local
x-envoy-attempt-count: 1
x-b3-traceid: 783afd2de5014b832abc544476021379
x-b3-spanid: 2abc544476021379
x-b3-sampled: 1

 先ほどまでは、サイドカーであるPodからの通信を暗号化していましたが、平文での通信となりました。送信元にサイドカープロキシが存在するサービスメッシュ内の通信なので、Envoyの情報がパケットに入っていることも確認できます。

 パケットキャプチャーの内容から相互TLS認証機能を使用して通信が暗号化されることを確認できました。興味のある方はぜひ本コラムを参考にパケットの内容を確認してみてください。


セキュリティ機能の実践【3】JWT検証

 ここから3つ目の機能として、JWTでのアクセストークンの検証を見ていきます。JWTの検証はRequestAuthenticationで設定することができ、「jwtRules」で「Issuer」を指定できます。RequestAuthenticationはエンドユーザーの認証に使用されるので、Ingress Gatewayで設定するといいでしょう。

 本稿では、以下の流れで設定前後の動作の違いを確認していきます。なおJWT検証は4つ目のセキュリティ機能「アクセス制御機能(認可機能)」を組み合わせることで実現する必要があるので、まずは項番2までを設定し、JWTを検証する動作を確認します。

項番 機能分類 設定/検証内容 確認内容
1 JWT検証(RequestAuthentication) 1.事前確認 ・トークンの有無に関係なく、Bookinfoアプリケーションへの通信が可能であること
2 2.RequestAuthenticationの適用 ・誤ったトークンを用いた場合、Bookinfoアプリケーションへの通信が失敗すること
・トークンがない場合も通信が可能であること
3 アクセス制御機能(認可機能) AuthorizationPolicyの適用 ・誤ったトークンを用いた場合、Bookinfoアプリケーションへの通信が失敗すること
・トークンがない場合も通信が失敗すること

1.事前確認

 まずは、事前確認としてトークンの有無に関係なく、Bookinfoアプリケーションへの通信が可能であることを確認します。期待される動作は、次の通りです。

  • アクセストークンなし:通信可能
  • アクセストークンあり(誤ったトークン):通信可能
# アクセストークンなしで外部からproductpageに通信
curl http://${INGRESS_HOST}:${INGRESS_PORT}/productpage -o /dev/null -w '%{http_code}\n' -s
200
 
#  アクセストークンあり(誤ったトークン)で外部からproductpageに通信
curl http://${INGRESS_HOST}:${INGRESS_PORT}/productpage -o /dev/null -H "Authorization: Bearer invalidToken" -w '%{http_code}\n' -s
200

2.RequestAuthenticationの適用

 ここからRequestAuthenticationによる認証設定を利用して、JWTで認証していきます。

・JWTを使用した認証設定

 本記事では、Ingress Gatewayへのアクセスに際して、Auth0で発行したテスト用のトークンを要求するように指定します。Auth0の具体的な説明は割愛しますが、興味ある方は公式ドキュメントを参照してください。

 まずはAuth0にログインし、「APIs」の「Menu」から新規APIを作成します。

「Application」欄内の「APIs」を押下

「Create API」を押下

 情報を入力し、「Create」を押下します。今回はサンプルとして、以下のような名前で作成します。

  • API名:istio-sample
  • 識別子:https://istio-sample.example.com

 認証APIが出来上がったら「Test」タブを開きます。

 作成したAPIを使って認証できるサンプルがあるのでcurlコマンドを使ってトークンを発行します。下記は一例なので、Testタブで確認される情報を使用してトークンを変数に格納してください。

# トークンを発行し、変数に格納する
export TOKEN=$(curl --request POST \
  --url https://istio-sample.jp.auth0.com/oauth/token \
  --header 'content-type: application/json' \
  --data '{"client_id":"XXXXXXXXX","client_secret":"XXXXXXXXX","audience":"https://istio-sample.example.com","grant_type":"client_credentials"}' | jq -r .access_token)

 トークンが発行されるのを確認できました。このトークンを使って、Istioでの動作を確認します。

 Auth0で発行されるトークンをIstioが検証できるようにRequestAuthenticationを設定する必要があります。「issuer」にはJWTの発行元を設定し、「jwksUri」はJWTを検証する公開鍵情報のURIを指定します。issuerはトークンをデコードし、「iss」を確認するとよいでしょう。デコードは、Auth0公式を使うと簡単に確認できます。また、jwksUriはAuth0のドキュメントで確認できます。

 yamlに反映すると次のようになります。

apiVersion: security.istio.io/v1beta1
kind: RequestAuthentication
metadata:
  name: "jwt-example"
  namespace: istio-system
spec:
  selector:
    matchLabels:
      istio: ingressgateway
  jwtRules:
  - issuer: https://istio-sample.jp.auth0.com/ # 発行元を設定
    jwksUri: https://istio-sample.jp.auth0.com/.well-known/jwks.json # 公開鍵情報のURIを指定
RequestAuthentication.yaml

 準備ができたら、マニフェストファイルを適用します。

$ kubectl apply -f ./RequestAuthentication.yaml
requestauthentication.security.istio.io/jwt-example created
 
$ kubectl get requestauthentications.security.istio.io -n istio-system
NAME          AGE
jwt-example   17s

・通信の確認

 次のパターンで通信してみましょう。

  • アクセストークンなし
  • アクセストークンあり(正しいトークン)
  • アクセストークンあり(誤ったトークン)

# アクセストークンなしで外部からproductpageに通信
$ curl http://${INGRESS_HOST}:${INGRESS_PORT}/productpage -o /dev/null -w '%{http_code}\n' -s
200
 
# アクセストークンあり(正しいトークン)で外部からproductpageに通信
$ curl http://${INGRESS_HOST}:${INGRESS_PORT}/productpage -o /dev/null -H "Authorization: Bearer $TOKEN" -w '%{http_code}\n' -s
200
 
# アクセストークンあり(誤ったトークン)で外部からproductpageに通信
$ curl http://${INGRESS_HOST}:${INGRESS_PORT}/productpage -o /dev/null -H "Authorization: Bearer invalidToken" -w '%{http_code}\n' -s
401

 誤ったトークンでは認証に失敗しますが、トークンなしでもアクセスできてしまいました。これは、トークンが正しいものかどうかを検証する設定は入っている一方で、各サービスへのアクセスには利用していないからです。AuthorizationPolicyを使用して、認可の設定を入れます。

セキュリティ機能の実践【4】アクセス制御機能(認可機能)

 ここからは最後の機能であるアクセス制御機能(認可機能)を実践します。Istioではリクエスト元のサービスのアクセス権限を確認した上でリクエストを「許可/拒否する」認可処理をここまで設定してきたJWT検証機能などを組み合わせることで実現できます。

 「セキュリティ機能の実践【3】JWT検証」から引き続いた設定として、下表の項番3に示した内容を確認します。

項番 機能分類 設定/検証内容 確認内容
1 JWT検証(RequestAuthentication) 1.事前確認 ・トークンの有無に関係なく、Bookinfoアプリケーションへの通信が可能であること
2 2.RequestAuthenticationの適用 ・誤ったトークンを用いた場合、Bookinfoアプリケーションへの通信が失敗すること
・トークンがない場合、通信が可能であること
3 アクセス制御機能(認可機能) 3.AuthorizationPolicyの適用 ・誤ったトークンを用いた場合、Bookinfoアプリケーションへの通信が失敗すること
・トークンがない場合、通信が失敗すること

3.AuthorizationPolicyの適用(JWT検証との組み合わせ)

 先ほどは、RequestAuthenticationでトークンを使用した認証を設定しました。ここから認可の設定を入れていきます。

・特定のトークンがある場合のみ許可する設定

 認可の設定にはAuthorizationPolicyを使用します。該当のトークンを使用した場合のみ、アクセスを許可する設定を入れます。

apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: "frontend-ingress"
  namespace: istio-system
spec:
  selector:
    matchLabels:
      istio: ingressgateway
  action: ALLOW                           # 該当のトークンでのアクセスを許可する設定
  rules:                                                  # ポリシーのルールを設定
  - when:
    - key: request.auth.audiences
      values:
      - https://istio-sample.example.com
AuthorizationPolicy_allow.yaml

 作成したリソースを適用します。

$ kubectl apply -f ./AuthorizationPolicy_allow.yaml
authorizationpolicy.security.istio.io/frontend-ingress created
 
$ kubectl get authorizationpolicies.security.istio.io -n istio-system
NAME               AGE
frontend-ingress   12s

・通信の確認

 先ほどと同じパターンで通信を発生させてみると、トークンなしでアクセスした場合の結果が異なることを確認できます。これは、先ほど設定したポリシーによってIngress Gatewayへのアクセスが拒否されたと判断することができるでしょう。

# アクセストークンなしで外部からproductpageに通信
$ curl http://${INGRESS_HOST}:${INGRESS_PORT}/productpage -o /dev/null -w '%{http_code}\n' -s
403
 
# アクセストークンあり(誤ったトークン)で外部からproductpageに通信
$ curl http://${INGRESS_HOST}:${INGRESS_PORT}/productpage -o /dev/null -H "Authorization: Bearer invalidToken" -w '%{http_code}\n' -s
401
 
# アクセストークンあり(正しいトークン)で外部からproductpageに通信
$ curl http://${INGRESS_HOST}:${INGRESS_PORT}/productpage -o /dev/null -H "Authori
zation: Bearer $TOKEN" -w '%{http_code}\n' -s
200

 このように、2つのリソースを組み合わせることで、独自に作り込むことなく認証機能を実現できます。公式ページ(JWT Token)にも手順や解説があるので、詳細が知りたい方は参照してください。

その他のAuthorizationPolicyの設定

 ここからは、AuthorizationPolicyを使用してさまざまな認可設定を試します。JWTの有無だけではなく、シンプルなアクセス許可/拒否のポリシーを設定できます。下表に示す流れで基本的な設定を見ていきます。

項番 設定/検証内容 確認内容
1 AuthorizationPolicyの設定(namespace、productpage) ・namespace全体に拒否ポリシー、productpageに許可ポリシーを設定し、外部からproductpageへのアクセスが可能であること
・productpageからdetailsサービスへのアクセスに失敗すること
2 AuthorizationPolicyの設定(detailsサービス) 項番1の状態において、productpage-detailsサービス間での許可ポリシーを設定し、アクセスが成功すること

AuthorizationPolicyの設定(namespace、アプリケーション単位)

 まずは、namespace:sample-appに対するリクエストを全て拒否するポリシーを設定します。マニフェスト上でルールが設定されていない場合、対象に対する拒否が設定されます。

apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: deny-all
  namespace: sample-app             # 対象は、namespace全体
spec:
  {}                # 設定しないと全て「拒否」になる
# - {}                              # 全て「許可」の場合は、左記のように指定

 次にproductpageのみにアクセスが可能となるように設定します。AuthorizationPolicyでポリシーを上書きすることによって、個別のサービスへの許可ポリシーが優先されます。

 また、下記YAMLではGETメソッドのみが許可されるように設定しています。設定できるルールの詳細は、Istio公式ページ(Authorization Policy)を確認してください。

apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: productpage-get
  namespace: sample-app
spec:
  selector:
    matchLabels:
      app: productpage          # 対象はproductpage
  action: ALLOW              # 許可設定
  rules:
  - to:
    - operation:
        methods: ["GET"]         # GETメソッドのみ許可

 マニフェストを適用し、productpageにアクセスします。ブラウザからの結果が分かりやすいでしょう。次のような状態になるはずです。

  • productpageへのアクセスは可能(Bookinfoのページが表示される)
  • reviewsサービス/detailsサービスなどのコンテンツは表示不可

$ kubectl apply -f ./AuthorizationPolicy_productpage_get.yaml
authorizationpolicy.security.istio.io/productpage-get created
 
$ kubectl get authorizationpolicies.security.istio.io -n sample-app
NAME              AGE
deny-all          12m
productpage-get   6s

・通信の確認

AuthorizationPolicyでのproductpageへの許可

 productpageのアクセスは許可されましたが、先ほど設定した拒否ポリシーが働いているので、後続のサービス(reviews、details、ratings)にアクセスできないことを示しています。

detailsへの許可設定(productpageサービスアカウントを持つサービスからのGETのみ)

 productpageからのアクセスのみが許可されるポリシーを作成し、動作を確認します。今回は、サービスアカウントを使用してポリシーを設定します。

apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: "details-viewer"
  namespace: sample-app
spec:
  selector:
    matchLabels:
      app: details         # 対象はdetails
  action: ALLOW        # ルールに一致した場合、許可される
  rules:
  - from:
    - source:         # リクエストの発行元のサービスアカウントを使用
        principals: ["cluster.local/ns/sample-app/sa/bookinfo-productpage"]
    to:
    - operation:
        methods: ["GET"]

 マニフェストを適用し、productpageにアクセスします。ブラウザからの結果を確認するといいでしょう。以下のような状態になるはずです。

$ kubectl apply -f ./AuthorizationPolicy_details-viewer.yaml
authorizationpolicy.security.istio.io/details-viewer created
 
$ kubectl get authorizationpolicies.security.istio.io -n sample-app
NAME              AGE
deny-all          13m
productpage-get   1m
details-viewer    10s

・通信の確認

  • productpageへのアクセスは可能(Bookinfoのページが表示される)
  • detailsサービスのコンテンツが表示される
  • reviewsサービスのコンテンツは表示されない

AuthorizationPolicyでのdetailsへの許可

 productpageからアクセスできることを確認できました。

 このように、基本的にはアクセスを禁止し、必要なものだけをリスト形式で許可することでセキュリティを向上させることができます。

まとめ

 本稿ではマイクロサービスに潜むセキュリティリスクとそれを解決するIstioのセキュリティ機能について解説し、認証や認可の機能を実践しました。

 マイクロサービスはさまざまなメリットが得られる一方で新たなセキュリティリスクが生まれます。アプリケーションで実装するだけでなく、Istioを活用して実装してみてください。今回紹介した内容以外にもより細やかなセキュリティ設定も可能ですので、興味ある方はIstio公式ページ(Security)を確認してください。

 また、今回で、4回にわたる「サービスメッシュ、Istio」はいったん終わりです。本稿がサービスメッシュおよびIstioを使いこなす一助となれば幸いです。

Copyright © ITmedia, Inc. All Rights Reserved.

スポンサーからのお知らせPR

注目のテーマ

4AI by @IT - AIを作り、動かし、守り、生かす
Microsoft & Windows最前線2025
AI for エンジニアリング
ローコード/ノーコード セントラル by @IT - ITエンジニアがビジネスの中心で活躍する組織へ
Cloud Native Central by @IT - スケーラブルな能力を組織に
システム開発ノウハウ 【発注ナビ】PR
あなたにおすすめの記事PR

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。