v1.23 へのアップグレード前に Webhook 証明書の互換性を確保する


バージョン 1.23 以降、Kubernetes は証明書の X.509 Common Name(CN)フィールドを使用したサーバー ID 検証をサポートしなくなりました。代わりに、Kubernetes は X.509 サブジェクト代替名(SAN)フィールドの情報のみを使用します。

クラスタへの影響を防ぐには、クラスタを Kubernetes バージョン 1.23 にアップグレードする前に、Webhook のバックエンドと集約 API サーバーの SAN がなく互換性のない証明書を置き換える必要があります。

Kubernetes が SAN なしのバックエンド証明書のサポートを終了した理由

GKE はオープンソースの Kubernetes を運用しています。この Kubernetes は kube-apiserver コンポーネントを使用し、Transport Layer Security(TLS)を使用して Webhook や集約 API サーバー バックエンドに接続します。kube-apiserver コンポーネントは Go プログラミング言語で記述されています。

Go 1.15 より前は、TLS クライアントは 2 段階プロセスを使用して接続先のサーバーの ID を検証していました。

  1. サーバーの DNS 名(または IP アドレス)が、サーバーの証明書の SAN の 1 つとして存在するかどうかを確認します。
  2. または、サーバーの DNS 名(または IP アドレス)がサーバーの証明書の CN と等しいかどうかを確認します。

2011 年、CN フィールドに基づくサーバー ID 検証は、RFC 6125 により完全に非推奨になりました。ブラウザやその他のセキュリティ クリティカルなアプリケーションは、このフィールドを使用しません。

より幅広い TLS エコシステムに合わせて、Go 1.15 では検証プロセスからステップ 2 を削除しましたが、デバッグ スイッチ(x509ignoreCN=0)を残して以前の動作を有効にすることで、移行プロセスが容易になります。Kubernetes バージョン 1.19 は、Go 1.15 を使用してビルドされた最初のバージョンでした。バージョン 1.19 から 1.22 までの GKE クラスタでは、デバッグ スイッチがデフォルトで有効になっているため、影響を受ける Webhook と集約 API サーバー バックエンドの証明書を置き換える時間が長くなりました。

Kubernetes バージョン 1.23 は Go 1.17 でビルドされているため、デバッグ スイッチが削除されています。GKE がクラスタをバージョン 1.23 にアップグレードすると、クラスタのコントロール プレーンから、有効な SAN のある X.509 証明書を提供していない Webhook や集計 API サービスに接続できなくなります。

影響を受けるクラスタの特定

1.21.9 または 1.22.3 以上のパッチ バージョンを実行しているクラスタの場合

Cloud Logging が有効なパッチ バージョン 1.21.9 および 1.22.3 以降のクラスタの場合は、GKE は Cloud Audit Logs のログを提供して、影響を受けるバックエンドへのクラスタからの呼び出しを特定します。次のフィルタを使用してログを検索できます。

logName =~ "projects/.*/logs/cloudaudit.googleapis.com%2Factivity"
resource.type = "k8s_cluster"
operation.producer = "k8s.io"
"invalid-cert.webhook.gke.io"

影響を受ける証明書を使用してクラスタがバックエンドを呼び出していない場合、ログは表示されません。このような監査ログには、影響を受けるバックエンドのホスト名が含まれています。

以下は、デフォルトの名前空間の example-webhook という名前のサービスによってホストされている Webhook バックエンドのログエントリの例です。

{
  ...
  resource {
    type: "k8s_cluster",
    "labels": {
      "location": "us-central1-c",
      "cluster_name": "example-cluster",
      "project_id": "example-project"
    }
  },
  labels: {
    invalid-cert.webhook.gke.io/example-webhook.default.svc: "No subjectAltNames returned from example-webhook.default.svc:8443",
    ...
  },
  logName: "projects/example-project/logs/cloudaudit.googleapis.com%2Factivity",
  operation: {
    ...
    producer: "k8s.io",
    ...
  },
  ...
}

影響を受けるサービスのホスト名(example-webhook.default.svc など)は、invalid-cert.webhook.gke.io/ で始まるラベル名に接尾辞として含まれます。呼び出しを行ったクラスタの名前を resource.labels.cluster_name ラベルから取得することもできます。この例では、example-cluster 値が設定されています。

非推奨に関する分析情報

互換性のない証明書を使用しているクラスタは、非推奨に関する分析情報で確認できます。分析情報は、バージョン 1.22.6-gke.1000 以降を実行しているクラスタで使用できます。

他のクラスタ バージョン

1.22 マイナー バージョンで 1.22.3 より前のパッチ バージョンまたは 1.21.9 より前のパッチ バージョンにクラスタがある場合、クラスタがサポート終了の影響を受けているかどうか判断するには、次の 2 つの方法があります。

オプション 1(推奨): 影響を受けるログを含む証明書の特定をサポートするパッチ バージョンにクラスタをアップグレードします。クラスタで Cloud Logging が有効になっていることを確認します。クラスタがアップグレードされると、適切な SAN を含む証明書を提供しない Service をクラスタが呼び出すたびに、識別用の Cloud Audit Logs のログが生成されます。ログは呼び出しの試行に対してのみ生成されるため、すべての呼び出しパスを呼び出すために十分な時間を確保できるよう、アップグレード後 30 日間待機することをおすすめします。

影響を受けるサービスを識別するには、ログを使用することをおすすめします。この方法を使用すると、影響を受けるサービスを表示するためのログを自動的に生成することで、手動作業が最小限に抑えられるからです。

オプション 2: クラスタの Webhook または集約 API サーバーで使用される証明書を検査して、SAN がないことが原因で影響を受けるかどうかを確認します。

  1. クラスタ内の Webhook と集約 API サーバーのリストを取得し、バックエンド(サービスまたは URL)を特定します。
  2. バックエンド サービスで使用される証明書を検査します。

この方法ですべての証明書を検査するには手動作業が必要となるため、クラスタをバージョン 1.21 にアップグレードする前に、Kubernetes バージョン 1.23 のサポート終了の影響を評価する必要がある場合のみ、この方法を使用してください。クラスタを 1.21 にアップグレードできる場合は、まずクラスタをアップグレードしてから、オプション 1 の手順に沿って手動の作業を回避する必要があります。

検査するバックエンド サービスの特定

サポート終了の影響を受ける可能性があるバックエンドを特定するには、Webhook と集約 API サービスのリストと、クラスタ内の関連するバックエンドを取得します。

クラスタ内の関連する Webhook すべての一覧を表示するには、次の kubectl コマンドを使用します。

kubectl get mutatingwebhookconfigurations -A   # mutating admission webhooks

kubectl get validatingwebhookconfigurations -A # validating admission webhooks

特定の Webhook に関連するバックエンド サービスまたは URL を取得するには、Webhook の構成の clientConfig.service フィールドまたは webhooks.clientConfig.url フィールドを調べます。

kubectl get mutatingwebhookconfigurations example-webhook -o yaml

このコマンドの出力は、次のようになります。

apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
webhooks:
- admissionReviewVersions:
  clientConfig:
    service:
        name: example-service
        namespace: default
        port: 443

なお、clientConfig は、バックエンドを Kubernetes Service(clientConfig.service)または URL(clientConfig.url)として指定できます。

クラスタ内の関連するすべての集約 API サービスを一覧表示するには、次の kubectl コマンドを使用します。

kubectl get apiservices -A |grep -v Local      # aggregated API services

このコマンドの出力は、次のようになります。

NAME                     SERVICE                      AVAILABLE   AGE
v1beta1.metrics.k8s.io   kube-system/metrics-server   True        237d

この例では、kube-system 名前空間から metric-server Service を返します。

特定の集約 API に関連する Service は、spec.service フィールドを調べることで取得できます。

kubectl get apiservices v1beta1.metrics.k8s.io -o yaml

このコマンドの出力は、次のようになります。

...
apiVersion: apiregistration.k8s.io/v1
kind: APIService
spec:
  service:
    name: metrics-server
    namespace: kube-system
    port: 443

Service の証明書を検査する

検査する関連バックエンド サービスを特定したら、example-service などの特定の Service の証明書を検査できます。

  1. Service のセレクタとターゲット ポートを確認します。

    kubectl describe service example-service
    

    このコマンドの出力は、次のようになります。

    Name: example-service
    Namespace: default
    Labels: run=nginx
    Selector: run=nginx
    Type: ClusterIP
    IP: 172.21.xxx.xxx
    Port: 443
    TargetPort: 444
    

    この例では、example-service にセレクタ run=nginx とターゲット ポート 444 があります。

  2. セレクタに一致する Pod を見つけます。

    kubectl get pods --selector=run=nginx
    

    コマンドの出力は、次のようになります。

    NAME          READY   STATUS    RESTARTS   AGE
    example-pod   1/1     Running   0          21m
    
  3. ポート転送

    kubectl localhost から Pod に設定します。

    kubectl port-forward pods/example-pod LOCALHOST_PORT:TARGET_PORT # port forwarding in background
    

    コマンドを次のように置き換えます。

    • LOCALHOST_PORT: リッスンするアドレス。
    • TARGET_PORT: ステップ 1 の TargetPort
  4. openssl を使用して、Service が使用する証明書を出力します。

    openssl s_client -connect localhost:LOCALHOST_PORT </dev/null | openssl x509 -noout -text
    

    次の出力例は、(SAN エントリを含む)有効な証明書を示しています。

    Subject: CN = example-service.default.svc
    X509v3 extensions:
      X509v3 Subject Alternative Name:
        DNS:example-service.default.svc
    

    次の出力例は、SAN がない証明書を示しています。

    Subject: CN = example-service.default.svc
      X509v3 extensions:
          X509v3 Key Usage: critical
              Digital Signature, Key Encipherment
          X509v3 Extended Key Usage:
              TLS Web Server Authentication
          X509v3 Authority Key Identifier:
              keyid:1A:5F:29:D8:E9:3C:54:3C:35:CC:D8:AB:D1:21:FD:C3:56:25:C0:74
    
  5. 次のコマンドを使用して、ポート転送を削除してバックグラウンドで実行されないようにします。

    $ jobs
    [1]+  Running                 kubectl port-forward pods/example-pod 8888:444 &
    $ kill %1
    [1]+  Terminated              kubectl port-forward pods/example 8888:444
    

URL バックエンドの証明書を検査する

Webhook が url バックエンドを使用する場合は、URL で指定されたホスト名に直接接続します。たとえば、URL が https://example.com:123/foo/bar の場合は、次の openssl コマンドを使用して、バックエンドで使用される証明書を出力します。

  openssl s_client -connect example.com:123 </dev/null | openssl x509 -noout -text

1.23 アップグレードのリスクの軽減

SAN のない証明書を使用して、影響を受けるクラスタとそのバックエンド サービスを特定したら、クラスタをバージョン 1.23 にアップグレードする前に、適切な SAN を持つ証明書を使用するように Webhook と集約 API サーバー バックエンドを更新する必要があります。

証明書を置き換えるか、バージョン 1.22サポート終了になるまで、GKE はバージョン 1.22.6-gke.1000 以降のクラスタを互換性のない証明書を使用してバックエンドで自動的にアップグレードしません。

クラスタが 1.22.6-gke.1000 より前の GKE バージョンを使用するには、メンテナンスの除外を構成してマイナー アップグレードを回避することで一時的な自動アップグレードを防ぐことができます。

リソース

この変更について詳しくは、次のリソースをご覧ください。

  • Kubernetes 1.23 リリースノート
    • Kubernetes は Go 1.17 を使用してビルドされています。このバージョンの Go では、GODEBUG=x509ignoreCN=0 環境設定を使用して、X.509 提供証明書の CN をホスト名として扱う非推奨の従来の動作を再び有効にする機能が削除されています。
  • Kubernetes 1.19Kubernetes 1.20 のリリースノート
    • SAN が存在しない場合に、X.509 の証明書の CN フィールドをホスト名として扱うという従来の非推奨の動作は、デフォルトで無効になりました。
  • Go 1.17 リリースノート
    • 一時的な GODEBUG=x509ignoreCN=0 フラグが削除されました。
  • Go 1.15 リリースノート
    • SAN が存在しない場合に、X.509 証明書の CN フィールドをホストとして扱うという従来の非推奨動作が、デフォルトで無効になりました。
  • RFC 6125 (46 ページ)
    • CN 値を使用するのがこれまでの方法でしたが、非推奨になりました。代わりに認証局では subjectAltName 値を指定することをおすすめします。
  • アドミッション Webhook