Amazon Web Services ブログ

Amazon EKS 上の AWS App Mesh での SPIFFE/SPIRE による mTLS の使用

本記事は、Efe Selcuk、Apurup Chevuru、Michael Hausenblas による Using mTLS with SPIFFE/SPIRE in AWS App Mesh on Amazon EKS を翻訳したものです。

AWS では、セキュリティを最優先事項と考え、責任共有モデルの観点から、お客様の責任部分をケアするためのコントロールを提供しています。サービスメッシュの一般的な使用例の 1 つは、通信経路のセキュリティ対策を強化することが挙げられますが、これは AWS App Mesh でも重点的に取り組んでいます。また、mTLS を安全かつ正しく利用するという課題は、実務者の間でも議論の対象となっています。AWS は App Mesh roadmap からの mTLS (mutual TLS、相互 TLS) の要望に応え、この機能のサポートを開始しました。このブログ記事では、mTLS の背景を説明し、Amazon Elastic Kubernetes Service (EKS) クラスターを使用したエンドツーエンドの例を紹介します。

背景

mTLS にあまり詳しくない場合は、このセクションをご覧ください。そうでない場合は、ウォークスルーに進んでください。

SPIFFE (Secure Production Identity Framework for Everyone) プロジェクトは、CNCF (Cloud Native Computing Foundation) のオープンソースプロジェクトであり、コミュニティから広く支持されています。このプロジェクトは、きめ細かく動的なワークロード ID 管理を提供します。SPIFFE のリファレンス実装である SPIRE に基づいて、あらゆる種類の分散システムにおいて、暗号的に強固で証明可能なアイデンティティを割り当てたり、問い合わせたりすることができます。この分野では SPIRE だけがオプションではないことに注意してください。例えば、Using EKS encryption provider support for defence-in-depth で説明しているように、暗号化に Kubernetes Secrets を使用することもできますが、この記事では SPIRE に焦点を当てます。

SPIFFE/SPIRE の用語を少しだけ紹介して、認識を合わせましょう。

  • ワークロードとは、特定の構成でデプロイされたソフトウェアのことで、例えば、コンテナとしてパッケージされ配信されるマイクロサービスです。
  • ワークロードは、クラスターや企業のネットワーク全体といった信頼ドメインのコンテキストで定義されます。
  • SPIFFE ID は、ワークロードのアイデンティティを spiffe://trust-domain/workload-identifier の形式で表します。
  • SVID (SPIFFE Verifiable Identity Document) は、ワークロードが自分の身元を証明するための文書であり、 信頼ドメイン内の署名機関によって署名されている場合に有効と見なされます。SVID インスタンスの一般的な例は、X.509 証明書です。
  • SPIFFE ワークロード API は、AWS EC2 インスタンスメタデータ API が AWS 固有の方法で提供しているのと同様に、サービスを識別するためのプラットフォームに依存しない方法を提供します。

さらに詳しく知りたい方は、Evan Gilman 氏による動画 Introduction to SPIFFE and SPIRE Projects をご覧ください。10 分足らずで、これらすべてがどのように連携するかを説明しています。

App Mesh における mTLS

App Mesh のコンテキストでの一般的な構成は、以下のようになります。

App Mesh はデータプレーンではプロキシとして機能する Envoy を使用し、あらゆる種類のトラフィックをインターセプトします。mTLS を有効にすることで、Envoy プロキシ間の通信は TLS で認証されますが [1]、サービスとその Envoy プロキシ間の通信は平文です [2]。

mTLS 認証は、L4/TCP、HTTP (1.1/2)、gRPC など、AWS App Mesh がサポートするすべてのプロトコルで使用できます。2 つの mTLS を 2 つのモードでサポートしています。

サーバーエンドポイントの TLS 設定の PERMISSIVE モードでは、エンドポイントへの平文トラフィックの接続を許可します。主に移行のためのもので、この記事の最後ではこちらに戻ります。

STRICT モードは暗号化されたトラフィックを強制するもので、今後はこちらがデフォルトと考えてください。

App Mesh は、サーバー検証による相互 TLS 認証のために、リスナー TLS 構成での 2 つの証明書ソースをサポートしています。この証明書ソースは、Envoy プロキシのローカルファイルシステム、または SPIRE を介した Envoy の Secret Discovery Service (SDS) API のいずれかから取得することができます。なお、App Mesh は、mTLS 認証に使用される機密データをメモリ上にのみ保存します。

具体的な利用シナリオを考えてみましょう。例えば、消費者の支払いを処理するアプリケーションでは、PCI DSS (Payment Card Industry Data Security Standard) に準拠することが要件の 1 つになっている場合があります。mTLS を使用すれば、そのボックスにチェックマークを付けて、手間のかかる作業を私たちに任せることができます。

mTLS がなぜ有益なのか、どのように機能するのかを App Mesh のコンテキストで高いレベルで理解したところで、具体的な例に移りましょう。

mTLS のウォークスルー

準備として、aws-app-mesh-examples.git リポジトリをクローンします。以下のセットアップは、howto-k8s-mtls-sds-based ウォークスルーに基づいています。環境変数 AWS_ACCOUNT_IDAWS_DEFAULT_REGION が設定されていることを確認してください。後でサンプルアプリのコンテナイメージをビルドして ECR にプッシュする際に必要になります。加えて、Docker が実行されていることも確認してください。

まず、以下のような eks-cluster-config.yaml 設定ファイルを使って、App Mesh が有効な EKS クラスターを作成します。

apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata:
  name: mtls-demo
  region: eu-west-1
  version: '1.18'
iam:
  withOIDC: true
  serviceAccounts:
  - metadata:
      name: appmesh-controller
      namespace: appmesh-system
      labels: {aws-usage: "application"}
    attachPolicyARNs:
    - "arn:aws:iam::aws:policy/AWSAppMeshFullAccess"
managedNodeGroups:
- name: default-ng
  minSize: 1
  maxSize: 3
  desiredCapacity: 2
  labels: {role: mngworker}
  iam:
    withAddonPolicies:
      certManager: true
      cloudWatch: true
      appMesh: true
cloudWatch:
  clusterLogging:
    enableTypes: ["*"]

以下のコマンドを実行して、EKS クラスターを作成します。

$ eksctl create cluster -f eks-cluster-config.yaml
[ℹ]  eksctl version 0.34.0
[ℹ]  using region eu-west-1
...
[✔]  EKS cluster "mtls-demo" in "eu-west-1" region is ready

次に、以下のコマンドを使用して AWS App Mesh controller for Kubernetes をインストールします。

まず、CRD を配置します。

helm repo add eks https://aws.github.io/eks-charts

kubectl apply -k "https://github.com/aws/eks-charts/stable/appmesh-controller/crds?ref=master"

なお、すでに Helm リポジトリが構成されている場合は、CRD を適用する前に helm repo update を実行してください。

CRD がインストールされたことを確認します。

$ kubectl api-resources --api-group=appmesh.k8s.aws -o wide 
NAME              SHORTNAMES   APIGROUP          NAMESPACED   KIND             VERBS
gatewayroutes                  appmesh.k8s.aws   true         GatewayRoute     [delete deletecollection get list patch create update watch]
meshes                         appmesh.k8s.aws   false        Mesh             [delete deletecollection get list patch create update watch]
virtualgateways                appmesh.k8s.aws   true         VirtualGateway   [delete deletecollection get list patch create update watch]
virtualnodes                   appmesh.k8s.aws   true         VirtualNode      [delete deletecollection get list patch create update watch]
virtualrouters                 appmesh.k8s.aws   true         VirtualRouter    [delete deletecollection get list patch create update watch]
virtualservices                appmesh.k8s.aws   true         VirtualService   [delete deletecollection get list patch create update watch]

次に、AWS App Mesh controller for Kubernetes のコントローラー本体をインストールしましょう。

$ helm upgrade -i appmesh-controller eks/appmesh-controller \
               --namespace appmesh-system \
               --set region=eu-west-1 \
               --set serviceAccount.create=false \
               --set serviceAccount.name=appmesh-controller \
               --set sds.enabled=true
Release "appmesh-controller" does not exist. Installing it now.
NAME: appmesh-controller
LAST DEPLOYED: Wed Feb 10 11:44:03 2021
NAMESPACE: appmesh-system
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
AWS App Mesh controller installed!

オプションですが、インストールされているコントローラーのバージョン (v1.3.x 以上である必要があります) を確認することできます。

kubectl -n appmesh-system get deployment appmesh-controller -o json \ | 
        jq -r ".spec.template.spec.containers[].image" \ | 
        cut -f2 -d ':'

次に、SPIRE サーバーを StatefulSet として、SPIRE エージェントを DaemonSet としてワーカーノードごとに 1 つずつ、あらかじめ設定された信頼ドメイン howto-k8s-mtls-sds-based.aws を使用してインストールします。

kubectl apply -f https://raw.githubusercontent.com/aws/aws-app-mesh-examples/master/walkthroughs/howto-k8s-mtls-sds-based/spire/spire_setup.yaml

なお、単一クラスターシナリオ用に調整された Helm チャートも用意していますので、SPIRE のセットアップに利用できます。

次に、SPIRE のセットアップを確認します。すなわち、すべての Pod が稼働していることを確認します。

$ kubectl -n spire get all
NAME                    READY   STATUS    RESTARTS   AGE
pod/spire-agent-gs2wp   1/1     Running   0          43s
pod/spire-agent-hwcbz   1/1     Running   0          43s
pod/spire-server-0      1/1     Running   0          44s

NAME                   TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
service/spire-server   NodePort   10.100.14.174   <none>        8081:31939/TCP   43s

NAME                         DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR   AGE
daemonset.apps/spire-agent   2         2         2       2            2           <none>          43s

NAME                            READY   AGE
statefulset.apps/spire-server   1/1     44s

これで、ヘルパースクリプト register_server_entries.sh を使用して、エージェントとワークロードを登録できます。

$ ./register_server_entries.sh register
Registering an entry for spire agent...
Entry ID      : 9dfa0073-2c11-427b-ad6b-dacae74b5b9d
SPIFFE ID     : spiffe://howto-k8s-mtls-sds-based.aws/ns/spire/sa/spire-agent
Parent ID     : spiffe://howto-k8s-mtls-sds-based.aws/spire/server
TTL           : 3600
Selector      : k8s_sat:cluster:k8s-cluster
Selector      : k8s_sat:agent_ns:spire
Selector      : k8s_sat:agent_sa:spire-agent

Registering an entry for the front app...
Entry ID      : 4a2310cb-a16a-4105-afae-e39d8872e5ba
SPIFFE ID     : spiffe://howto-k8s-mtls-sds-based.aws/front
Parent ID     : spiffe://howto-k8s-mtls-sds-based.aws/ns/spire/sa/spire-agent
TTL           : 3600
Selector      : k8s:ns:howto-k8s-mtls-sds-based
Selector      : k8s:sa:default
Selector      : k8s:pod-label:app:front
Selector      : k8s:container-name:envoy

...

なお、登録されているエンティティは、以下のコマンドでいつでも一覧表示することができます。

kubectl exec -n spire spire-server-0 \
             -c spire-server -- \
             /opt/spire/bin/spire-server entry show

最後に、ヘルパースクリプト deploy_app.sh を使用して、接続性をテストするサンプルアプリをデプロイします (訳注: ENVOY_IMAGE 環境変数についてエラーが発生した場合はこちらを参照して設定して下さい) 。

$ ./deploy_app.sh
CRD check passed!
aws-app-mesh-controller check passed! v1.3.0 >= v1.3.0
deploy images...
Login Succeeded
Sending build context to Docker daemon  3.584kB
...
7f03bfe4d6dc: Pushed
latest: digest: sha256:c2ea478c3ca7d1b6ade35f9639d257d8e3be831d1e41de20e1e959a945cd74ca size: 2631
...
namespace/howto-k8s-mtls-sds-based created
mesh.appmesh.k8s.aws/howto-k8s-mtls-sds-based created
...
service/color-red created
deployment.apps/red created
service/color created

すべての Pod が起動して実行中であり、AWS App Mesh controller for Kubernetes が管理するカスタムリソースが以下のようになっていることを確認してください。

$ kubectl -n howto-k8s-mtls-sds-based get all
NAME                         READY   STATUS    RESTARTS   AGE
pod/blue-6f7c4d4757-qz6cq    2/2     Running   0          28m
pod/front-74c86557b6-wpg2l   2/2     Running   0          28m
pod/green-65677456f5-66c6l   2/2     Running   0          28m
pod/red-849ffcbd75-84q5w     2/2     Running   0          28m

NAME                  TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
service/color         ClusterIP   10.100.119.181   <none>        8080/TCP   28m
service/color-blue    ClusterIP   10.100.248.144   <none>        8080/TCP   28m
service/color-green   ClusterIP   10.100.97.188    <none>        8080/TCP   28m
service/color-red     ClusterIP   10.100.168.32    <none>        8080/TCP   28m
service/front         ClusterIP   10.100.33.255    <none>        8080/TCP   28m

NAME                    READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/blue    1/1     1            1           28m
deployment.apps/front   1/1     1            1           28m
deployment.apps/green   1/1     1            1           28m
deployment.apps/red     1/1     1            1           28m

NAME                               DESIRED   CURRENT   READY   AGE
replicaset.apps/blue-6f7c4d4757    1         1         1       28m
replicaset.apps/front-74c86557b6   1         1         1       28m
replicaset.apps/green-65677456f5   1         1         1       28m
replicaset.apps/red-849ffcbd75     1         1         1       28m

NAME                                  ARN                                                                                                                 AGE
virtualrouter.appmesh.k8s.aws/color   arn:aws:appmesh:eu-west-1:123456789012:mesh/howto-k8s-mtls-sds-based/virtualRouter/color_howto-k8s-mtls-sds-based   28m

NAME                                   ARN                                                                                                                                    AGE
virtualservice.appmesh.k8s.aws/color   arn:aws:appmesh:eu-west-1:123456789012:mesh/howto-k8s-mtls-sds-based/virtualService/color.howto-k8s-mtls-sds-based.svc.cluster.local   28m

NAME                                ARN                                                                                                               AGE
virtualnode.appmesh.k8s.aws/blue    arn:aws:appmesh:eu-west-1:123456789012:mesh/howto-k8s-mtls-sds-based/virtualNode/blue_howto-k8s-mtls-sds-based    28m
virtualnode.appmesh.k8s.aws/front   arn:aws:appmesh:eu-west-1:123456789012:mesh/howto-k8s-mtls-sds-based/virtualNode/front_howto-k8s-mtls-sds-based   28m
virtualnode.appmesh.k8s.aws/green   arn:aws:appmesh:eu-west-1:123456789012:mesh/howto-k8s-mtls-sds-based/virtualNode/green_howto-k8s-mtls-sds-based   28m
virtualnode.appmesh.k8s.aws/red     arn:aws:appmesh:eu-west-1:123456789012:mesh/howto-k8s-mtls-sds-based/virtualNode/red_howto-k8s-mtls-sds-based     28m

これで、mTLS を確認することができます。

$ kubectl -n default run -it --rm curler --image=tutum/curl /bin/bash
# まず、セキュリティで保護された(TLSが有効になっている)フロントエンド経由のパスを試します
root@curler:/# curl -H "color_header: blue" front.howto-k8s-mtls-sds-based.svc.cluster.local:8080; echo;
blue
# ここで、blueサービスに直接アクセスしてみます(strictモードのために失敗するはずです)
root@curler:/# curl -k https://color-blue.howto-k8s-mtls-sds-based.svc.cluster.local:8080 -v
* Rebuilt URL to: https://color-blue.howto-k8s-mtls-sds-based.svc.cluster.local:8080/
* Hostname was NOT found in DNS cache
*   Trying 10.100.50.219...
* Connected to color-blue.howto-k8s-mtls-sds-based.svc.cluster.local (10.100.50.219) port 8080 (#0)
* successfully set certificate verify locations:
*   CAfile: none
  CApath: /etc/ssl/certs
* SSLv3, TLS handshake, Client hello (1):
* SSLv3, TLS handshake, Server hello (2):
* SSLv3, TLS handshake, CERT (11):
* SSLv3, TLS handshake, Server key exchange (12):
* SSLv3, TLS handshake, Request CERT (13):
* SSLv3, TLS handshake, Server finished (14):
* SSLv3, TLS handshake, CERT (11):
* SSLv3, TLS handshake, Client key exchange (16):
* SSLv3, TLS change cipher, Client hello (1):
* SSLv3, TLS handshake, Finished (20):
* SSLv3, TLS alert, Server hello (2):
* error:14094410:SSL routines:SSL3_READ_BYTES:sslv3 alert handshake failure
* Closing connection 0
curl: (35) error:14094410:SSL routines:SSL3_READ_BYTES:sslv3 alert handshake failure

これで完了です!App Mesh のコンソールでもステータスを確認することができ、以下のように表示されるはずです。

クリーンアップするには、以下のコマンドを使用できます。

# サンプルを削除します
kubectl delete ns howto-k8s-mtls-sds-based
# さらに、SPIREを削除します
kubectl delete ns spire
# さらに、セットアップ全体(App MeshとEKSクラスター)を削除します
eksctl delete cluster --region=eu-west-1 --name=mtls-demo

使用上の考慮事項

上記のウォークスルーで見てきたように、EKS のコンテキストで App Mesh の新機能である mTLS を 使用するのは簡単です。これは、AWS が開発したコントローラーのおかげでもありますし、SPIRE がワークロードの ID 管理に関する面倒な作業を引き受けてくれているおかげでもあります。

SPIRE は、デフォルトでは 1 時間の短命な証明書を発行し、有効期限が切れる前に自動的に更新します(自動ローテーション)。証明書は、SPIRE エージェントによって Envoy プロキシにプッシュされます。

EKS 上の App Mesh のコンテキストでの mTLS の使用に関するその他の考慮事項をいくつか記載します。

  • 事前に計画を立てて、既存の (暗号化されていない) ワークロードの移行を検討する必要があります。
  • 上記のウォークスルーでは、自己署名証明書を使用した簡単なシナリオを示しましたが、AWS Certificate Manager (ACM) などの認証局 (CA) を使用することもできます。
  • EKS on Fargate のコンテキストで SPIRE を使用する場合、Fargate では Kubernetes の DaemonSet がまだサポートされていないため、上記のソリューションは使用できないことに注意してください。
  • その他の (関連する) 実践的なウォークスルーについて、App Mesh examples リポジトリを確認してください。

この新しい App Mesh のセキュリティ機能を使った感想をお聞かせください。ロードマップを通じて、フィードバックや提案の共有もお願いします。

翻訳はプロフェッショナルサービスの杉田が担当しました。