Kubernetesセキュリティ:クラスターを守るための必須ステップ

Security tutorial - IT technology blog
Security tutorial - IT technology blog

Kubernetesクラスターが攻撃対象になるとき

去年、痛い教訓を得た。深夜にSSHブルートフォース攻撃を受け、寝ぼけた頭で慌てて対応しなければならなかった。それ以来、習慣ができた――どのインフラを構築するにしても、セキュリティは初日から設定する。問題が起きてから考えるのでは遅い。

Kubernetesも例外ではない。クラスターを構築してアプリが動けばOK、そのままproductionにデプロイ――セキュリティは後回し。そして「後で」はたいてい、インシデントが発生するまで永遠に来ない。

これは理論の話ではない。2018年、RedLockチームはTeslaのKubernetesクラスターがAPIサーバーを認証なしでインターネットに公開していることを発見した――攻撃者はそれを利用して仮想通貨のマイニングに使った。コンテナはroot権限で動作し、Pod間は自由に通信でき、シークレットは暗号化なしのbase64で保存され、APIサーバーへのアクセス制限もなかった。Teslaだけの話ではなく、これはこれまでレビューした多くのproductionクラスターの実態だ。

なぜKubernetesはデフォルトで安全でないのか?

K8sは柔軟性と使いやすさを優先して設計されている。セキュリティは自分で追加するレイヤーであり、最初から備わっているものではない。具体的には:

  • RBACはデフォルトで有効でない場合がある――古いバージョンや、作成直後のマネージドクラスターなど。
  • PodはSecurityContextを指定しなければroot権限で動作する。
  • Network Policyは存在しない――自分で書くまで、クラスター内のすべてのPodが自由に通信できる。
  • シークレットはbase64に過ぎない:etcdまたはSecretリソースへの読み取り権限があれば、すべての内容を読める。
  • デフォルトのService AccountはすべてのPodにマウントされ、Kubernetes APIを呼び出せる。

これらを組み合わせると、コンテナ1つが侵害されるだけで、攻撃者はクラスター全体へ権限昇格できる。ラボで試したことがあるが、侵害されたPodからcluster-admin権限を得るまで10分もかからなかった。

設定すべきセキュリティレイヤー

1. RBAC — アクセス権限の制御

すべての基盤となるのは最小権限の原則だ:必要なものだけ、それ以上は与えない。

# 「app」namespace内でPodの読み取りのみを許可するRoleを作成
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: app
  name: pod-reader
rules:
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: read-pods
  namespace: app
subjects:
- kind: ServiceAccount
  name: my-service-account
  namespace: app
roleRef:
  kind: Role
  name: pod-reader
  apiGroup: rbac.authorization.k8s.io

ClusterRoleverbs: ["*"]resources: ["*"]を設定することは特に避けるべきだ――急いでセットアップした多くのクラスターで見かけるミスだ。余剰な権限を検出するために定期的にセキュリティ監査しよう:

# サービスアカウントの権限を確認
kubectl auth can-i --list --as=system:serviceaccount:app:my-service-account

# anonymousユーザーに割り当てられたClusterRoleBindingを検索
kubectl get clusterrolebindings -o json | jq '.items[] | select(.subjects[]?.name=="system:anonymous")'

2. Pod Security — コンテナの動作を制限する

Kubernetes 1.25以降、Pod Security AdmissionがPodSecurityPolicy(1.21で非推奨)に取って代わった。namespaceレベルでポリシーを適用する:

# productionnamespaceに「restricted」ポリシーを適用
kubectl label namespace production \
  pod-security.kubernetes.io/enforce=restricted \
  pod-security.kubernetes.io/audit=restricted \
  pod-security.kubernetes.io/warn=restricted

Pod/DeploymentのspecではSecurityContextを明示的に宣言すること:

securityContext:
  runAsNonRoot: true
  runAsUser: 1000
  readOnlyRootFilesystem: true
  allowPrivilegeEscalation: false
  capabilities:
    drop:
    - ALL

コンテナはUID 1000で動作し、ファイルシステムは読み取り専用、Linuxケーパビリティはすべて削除される。コンテナが侵害されても、攻撃者にできることは限られる――狭いサンドボックスの中に閉じ込められる。

3. Network Policy — Pod間のファイアウォール

デフォルトでは、PodAはPodBを理由なく直接呼び出せる。Network Policyを使えば、どのトラフィックを許可するかを正確に定義できる:

# frontendからbackendへの通信のみ許可、それ以外は禁止
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: backend-policy
  namespace: app
spec:
  podSelector:
    matchLabels:
      role: backend
  policyTypes:
  - Ingress
  - Egress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          role: frontend
    ports:
    - protocol: TCP
      port: 8080
  egress:
  - to:
    - podSelector:
        matchLabels:
          role: database
    ports:
    - protocol: TCP
      port: 5432

重要な注意点:Network PolicyはCNIプラグインのサポートが必要だ――Calico、Cilium、Weaveのいずれも対応している。Flannelを使っている場合は注意:ポリシーを作成しても一切効果がなく、警告も出ない。

4. シークレット管理 — Kubernetesのデフォルトシークレットを信用するな

base64は暗号化ではない。etcdまたはkubectl get secretの読み取り権限があれば、即座に平文を取得できる。対処の方向性は2つある:

方法1 — etcdの保存時暗号化を有効にする(コントロールプレーンへの権限が必要):

# EncryptionConfigurationを作成
cat > /etc/kubernetes/encryption-config.yaml <<EOF
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
  - resources:
    - secrets
    providers:
    - aescbc:
        keys:
        - name: key1
          secret: $(head -c 32 /dev/urandom | base64)
    - identity: {}
EOF

# kube-apiserverに追加: --encryption-provider-config=/etc/kubernetes/encryption-config.yaml

方法2(推奨) — External Secrets OperatorをHashiCorp VaultまたはAWS Secrets Managerと連携させる。シークレットはetcdに保存されず、実行時にのみPodにインジェクトされる:

# External Secrets Operatorをインストール
helm repo add external-secrets https://charts.external-secrets.io
helm install external-secrets external-secrets/external-secrets -n external-secrets --create-namespace

5. 監査ログとランタイムセキュリティ

異常な動作を早期に検出することは、防御と同じくらい重要だ。APIサーバーで監査ログを有効にする

# audit-policy.yaml
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
- level: Metadata
  resources:
  - group: ""
    resources: ["secrets", "configmaps"]
- level: RequestResponse
  verbs: ["delete", "create"]
  resources:
  - group: ""
    resources: ["pods"]

Falcoはランタイム検出レイヤーを追加する――コンテナ内のシェル起動、異常なファイルアクセス、不審なネットワーク接続をリアルタイムで検出する。productionでは欠かせないものだ:

helm repo add falcosecurity https://falcosecurity.github.io/charts
helm install falco falcosecurity/falco --namespace falco --create-namespace \
  --set falco.grpc.enabled=true \
  --set falco.grpcOutput.enabled=true

優先順位別チェックリスト

NSAとCISAがK8sセキュリティに関する共同アドバイザリを発表し、「銀の弾丸はない――複数の防御レイヤーを重ねることでのみ効果を発揮する」と結論付けた。1つのレイヤーを突破されても、次のレイヤーが止める。実際の優先順位は:

  1. APIサーバーをインターネットに公開しない ――VPNまたはbastionホストを使用する。最も深刻で、かつ最も簡単に修正できるミスだ。
  2. Kubernetesを定期的に更新する ――K8sはリリースサイクルでCVEにパッチを当てるため、古いバージョンには既知の脆弱性が存在する。
  3. 最小権限のRBAC ――定期的にレビューし、不要な権限を削除する。
  4. すべてのPodにSecurityContextを設定 ――見落としがないよう、HelmチャートまたはデフォルトテンプレートにRBACを組み込む。
  5. Network Policy ――deny-allから始め、実際のニーズに合わせて段階的に開放する。
  6. デプロイ前にイメージをスキャン ――TrivyまたはGrypeをCI/CDパイプラインに直接統合する。
  7. 適切なシークレット管理 ――最低でも保存時暗号化、理想的には外部ボールト。
  8. 監査ログ+ランタイム監視 ――productionではFalcoは必須、妥協なし。
# プッシュ前にTrivyでイメージをスキャン
trivy image --severity HIGH,CRITICAL your-image:tag

# YAMLマニフェストの設定ミスを確認
trivy config ./k8s-manifests/

# CIS Kubernetes BenchmarkでクラスターをAudit
docker run --pid=host --userns=host --rm -v /etc:/etc:ro \
  -v /var:/var:ro -v /usr/lib/systemd:/usr/lib/systemd:ro \
  aquasec/kube-bench:latest

チームによく強調していることがある:セキュリティはプロセスの一部でなければならない――個別のタスクではなく。TrivyをCIパイプラインに統合し、admission controllerでPod Securityを強制し、RBACレビューをスプリントレトロに組み込む――それが習慣になれば、もう重荷には感じなくなる。

Kubernetesクラスターが「完全に安全」な状態になることはない。しかし上記のステップを実践することで、リスクを許容できるレベルまで引き下げられる――そして何より重要なのは、被害が出た後ではなく、異常が発生したときにすぐ気づけるようになることだ。

Share: