ブログ

CVE-2020-8563: cloud-controller-manager ログにおける vSphere の資格情報の漏洩についての理解と緩和

CVE-2020-8563: cloud-controller-manager ログにおける vSphere の資格情報の漏洩についての理解と緩和

本文の内容は、2020年10月16日にKaizhe Huangが投稿したブログ(https://sysdig.com/blog/cve-2020-8563-vsphere-credentials-cloud-controller-manager/)を元に日本語に翻訳・再構成した内容となっております。

Kubernetes のソースコードを監査していたところ、最近、Kubernetes に機密データの漏洩を引き起こす可能性のある問題 (CVE-2020-8563) を発見しました。

vSphere 上に Kubernetes クラスターを作成し、ロギング レベルを 4 以上に設定してクラウドプロバイダとして vSphere を有効にした場合、CVE-2020-8563 の影響を受ける可能性があります。その場合、vSphere のユーザー資格情報が cloud-controller-manager のログに漏洩します。このログにアクセスできる人は誰でもこのログを読むことができ、あなたの vSphere ユーザーになりすまして、インフラストラクチャー全体を危険にさらすことができます。

この記事では、この問題について理解し、Kubernetes のどの部分が影響を受けるのか、そしてその緩和方法について説明します。

予備的な内容

まずは、問題の原因となる、または問題の影響を受ける Kubernetes コンポーネントをリストアップしてみましょう。

Kub-controller-managerKubernetes コントローラマネージャーは、状態の更新を監視し、それに応じてクラスターに変更を加えるコアコントローラの組み合わせです。現在、Kubernetesに同梱されているコントローラには以下のものがあります。

  • レプリケーションコントローラ:コントローラオブジェクトを複製するために、システム上の正しい数のポッドを維持します。
  • ノードコントローラ:ノードの変更を監視します。
  • エンドポイントコントローラ:サービスオブジェクトとポッドオブジェクトを結合するためのエンドポイントオブジェクトを生成します。
  • サービスアカウントとトークンコントローラ:ネームスペースのサービスアカウントとトークンを管理します。

クラウドコントローラマネージャ:v1.6 で導入されたもので、基盤となるクラウドプロバイダーと対話するためのコントローラを実行します。これは、クラウドベンダーのコードをKubernetesのコードから切り離す試みです。

Informer/SharedInformer:コントローラの重要なコンポーネント。彼らはKubernetesオブジェクトの現在の状態の変化を監視し、Workqueueにイベントを送信します。

Secret: Kubernetes Secretsを使うと、パスワード、OAuthトークン、sshキーなどの機密情報を保存して管理することができます。機密情報をSecretに保存することは、Podの定義やコンテナイメージにそのまま入れるよりも安全で柔軟性があります。

CVE-2020-8563 問題

問題のCVE-2020-8563です。Kubernetes クラスターを vSphere 上で実行していて、そのログレベルが 4 以上に設定されている場合、vSphere の資格情報は cloud-controller-manager によってログに記録されます。

ここでは、Kubernetes の関連するコードパスを歩いて、何が問題になったのかを説明します。

Kubernetesのソースコードの中には、「legacy-cloud-providers」というフォルダがあります。これには、クラウドコントローラーのレガシーコードが含まれています。AWS、Azure、GCE、OpenStack、vSphereです。これらは非推奨で、将来的には削除される予定ですが、今日まで使われています。例えば、kopsを使ってAWS上にKubernetesクラスターを作成した場合、以下のようにクラウドプロバイダーの非推奨に関するWARNINGメッセージが表示されます。

W0831 22:04:04.653364       1 plugins.go:115] WARNING: aws built-in cloud provider is now deprecated. The AWS provider is deprecated and will be removed in a future release

警告で明らかになったように、レガシーなクラウドプロバイダーを使用している可能性が高いです。

cloud-controller-managerが起動すると資格情報が漏れてしまいます。そこで、実行を追って実際にログを取っている行を探してみましょう。

cloud-controller-managerが起動すると、一連のコントローラも起動します。その間に、ユーザクラウドオブジェクトにインフォーマを設定します。

// startControllers start the cloud specific controller loops.
func startControllers(c *cloudcontrollerconfig.CompletedConfig, stopCh <-chan struct{}, cloud cloudprovider.Interface, controllers map[string]initFunc) error {
        // Initialize the cloud provider with a reference to the clientBuilder
        cloud.Initialize(c.ClientBuilder, stopCh)
        // Set the informer on the user cloud object
        if informerUserCloud, ok := cloud.(cloudprovider.InformerUser); ok {
                informerUserCloud.SetInformers(c.SharedInformers)
        }
        for controllerName, initFn := range controllers {
                if !genericcontrollermanager.IsControllerEnabled(controllerName, ControllersDisabledByDefault, c.ComponentConfig.Generic.Controllers) {
                        klog.Warningf("%q is disabled", controllerName)
                        continue
                }
                klog.V(1).Infof("Starting %q", controllerName)
                _, started, err := initFn(c, cloud, stopCh)
                if err != nil {
                        klog.Errorf("Error starting %q", controllerName)
                        return err
                }
                        klog.Warningf("Skipping %q", controllerName)
                        continue
                }
                klog.Infof("Started %q", controllerName)
... 

InformerUserは、実はインターフェースです:

type InformerUser interface {
  // SetInformers sets the informer on the cloud object.
  SetInformers(informerFactory informers.SharedInformerFactory)
} 

各クラウドプロバイダーが独自に実装している SetInformers機能を探してみると、AWS、Azure、GCE、vSphereの各クラウドプロバイダーがそれぞれ実装していることがわかります。

以下はvSphereの実装です。

1.png

以下はvSphereの実装です:

// Initialize Node Informers
func (vs *VSphere) SetInformers(informerFactory informers.SharedInformerFactory) {
        if vs.cfg == nil {
                return
        }
        if vs.isSecretInfoProvided {
                secretCredentialManager := &SecretCredentialManager{
                        SecretName:      vs.cfg.Global.SecretName,
                        SecretNamespace: vs.cfg.Global.SecretNamespace,
                        SecretLister:    informerFactory.Core().V1().Secrets().Lister(),
                        Cache: &SecretCache{
                                VirtualCenter: make(map[string]*Credential),
                        },
                }
                if vs.isSecretManaged {
                        klog.V(4).Infof("Setting up secret informers for vSphere Cloud Provider")
                        secretInformer := informerFactory.Core().V1().Secrets().Informer()
                        secretInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
                                AddFunc:    vs.SecretAdded,
                                UpdateFunc: vs.SecretUpdated,
                        })
                        klog.V(4).Infof("Secret informers in vSphere cloud provider initialized")
                }
... 

コードによると、vsphere.confファイルで「secret-name」と「secret-namespace」という属性が指定されている場合、条件「vs.isSecretInfoProvided」は真を返します。

vsphere.confファイルで設定できるもう1つの未記載の属性 "secret-not-managed "があります。デフォルトではこの属性はfalseだったので、"vs.isSecretManaged "条件は常にtrueを返します。

結果として、シークレットの作成や更新などのイベントを処理するための秘密情報提供者が常に存在することになります。

問題の原因に近づいています...。

「SecretAdded」関数では、以下のようになっています。

obj" 変数には、シークレット名、ネームスペース、および実際のシークレットデータなどのメタデータを含む、シークレットのすべての情報が格納されます。

これには、vsphere.conf ファイルの vSphere 資格情報が含まれます。vsphere のシークレット名とネームスペースに一致するシークレットは、以下のようにログに記録されます:

secret added: &Secret{ObjectMeta:{vsphere-secret  kube-system /api/v1/namespaces/kube-system/secrets/vsphere-secret 97345a64-db69-4adb-9444-e00e7345fd46 737933 0 2020-09-03 11:20:30 -0700 PDT <nil> <nil> map[] map[kubectl.kubernetes.io/last-applied-configuration:{"apiVersion":"v1","kind":"Secret","metadata":{"annotations":{},"name":"vsphere-secret","namespace":"kube-system"},"stringData":{"vsphere.conf":"username: administrator@vsphere.local\npassword: password"},"type":"Opaque"}

さらに調べてみると、「SecretUpdated」関数の中でも同様のことが起こっていることがわかりました。

CVE-2020-8563の影響

CVSSシステムによると、中程度の深刻度として5.6のスコアを付けています。

これは、秘密漏洩を起こすために、攻撃者が cloud-controller-manager のログレベルを4以上に設定する方法を見つける必要があることを考慮に入れています。このエクスプロイトは、コンポーネントが存在するノードへのアクセス権限を必要とするため、深刻度は中程度です。

2.png

しかし、cloud-controller-managerのログレベルが最初から4以上に設定されていたとしたらどうでしょうか?例えば、誰かがトラブルシューティングを支援するために(kubeadm 経由で)ログの冗長度を上げたとします。

すると、このCVEスコアの重大度が7.7と高重大度になってしまいます:

3.png

この場合、特別なアクセスは必要ありません。cloud-provider-controller のログにアクセスできる人は誰でも秘密を閲覧することができます。

まとめると、影響を受けたKubernetesクラスタは以下のような構成になっています。

  1. vSphere上に構築されたKubernetesクラスター
  2. cloud-controller-managerのログレベルは4以上に設定されています。
  3. 彼らは、vSphere の資格情報を格納するために Kubernetes のシークレットを使用しています。

全体的に、ログアーカイブにクリアテキストで保存されたシークレットは、ログアーカイブにアクセスできるユーザーやアプリケーションに公開される可能性があるため、危険です。vsphere の資格情報にアクセスできる人は、vsphere のユーザーに代わって行動することができます。シークレットが vSphere 管理者の資格情報を保存している場合(これが最も可能性が高い)、クラスター全体が危険にさらされる可能性があります。

CVE-2020-8563 の緩和

この CVE の影響を受けた場合は、直ちに vSphere パスワードを更新する必要があります。期限切れの認証情報を使用して vSphere データセンターにアクセスすることはできません。

cloud-controller-manager のログ レベルがデフォルト値に設定されていたとしても、kube-apiserver、kube-controller-manager のような Kubernetes コンポーネントが冗長なログで起動している可能性があることを確認する必要があります。

CNCFのインキュベーションプロジェクトであるFalcoは、クラウドネイティブ環境での異常なアクティビティの検出に役立ちます。以下のfalcoルールは、CVE-2020-8563の影響を受けているかどうかを検出するのに役立ちます:

- list: kube_controller_manager_image_list
  items:
  - "k8s.gcr.io/kube-controller-manager"




- macro: kube_controller_manager
  condition: (proc.name=kube-controller and container.image.repository in (kube_controller_manager_image_list))




- list: kube_apiserver_image_list
  items:
  - "k8s.gcr.io/kube-apiserver"




- macro: kube_apiserver
  condition: (proc.name=kube-apiserver and container.image.repository in (kube_apiserver_image_list))




- list: vsphere_cloud_manager_image_list
  items:
  - "gcr.io/cloud-provider-vsphere/cpi/release/manager"




- macro: vsphere_cloud_manager
  condition: (proc.name=vsphere-cloud-c and container.image.repository in (vsphere_cloud_manager_image_list))




# TODO: add more components like kube-proxy, kube-scheduler
- macro: kube_components
  condition: (kube_apiserver or kube_controller_manager or vsphere_cloud_manager)




# default log level is 2, any log level greater than 2 is considered verbose
- macro: verbose_log
  condition: ((evt.args contains "-v=" or evt.args contains "--v=") and not (evt.args contains "-v=0" or evt.args contains "-v=1" or evt.args contains "-v=2" or evt.args contains "--v=0" or evt.args contains "--v=1" or evt.args contains "--v=2"))




- rule: Kube Componentes Started with Verbose Log Level
  desc: Detected kube components like kube-apiserver, kube-proxy started with verbose log enabled
  condition: spawned_process and kube_components and verbose_log
  output: Kube components started with verbose log enabled (command=%proc.cmdline program=%proc.name container_id=%container.id image=%container.image.repository)
  priority: WARNING
  tags:
  - "process" 

このルールは、冗長なログから始まるKubernetesコンポーネントを検出します。

セキュリティイベントの出力は、Sysdig Secure (下にFalcoを使用しています)ではこのようになります。

4.png

まとめ

Kubernetesクラスターの頭脳である kube-apiserver や、Kubernetes クラスターに関する重要な情報をすべて保存している etcd を堅牢にさせるためのベストプラクティスに従っているかもしれません。しかし、CVE-2020-8563 が教えてくれることが一つあるとすれば、Kubernetes のすべてのコンポーネントを保護する必要があるため、それだけに留まるべきではないということです。

Sysdig Secureは、Kubernetesクラスターのベンチマークを行い、PCIやNISTなどのセキュリティ基準に準拠しているかどうかをチェックするのに役立ちます。今すぐお試しください。

ご質問・お問い合わせはこちら

top