はじめに
「ジャンルなしオンラインもくもく会 Advent Calendar 2025」の17日目の記事です。
Week 3の始まりとして、Kubernetesクラスタのセキュリティを強化します。これまでHelm/Kustomizeでマニフェスト管理し、ArgoCDでGitOpsを実現しましたが、セキュリティが不十分では本番運用できません。
今日は本番運用を想定してセキュリティ設定を実装します。
本シリーズの全体構成
前回までのおさらい
Kubernetesセキュリティの4C
Kubernetesセキュリティは**4つの層(4C)**で考えます
今回は Cluster層のセキュリティ を実装します。
1. Pod Security Standards(PSS)
Pod Security Standardsとは?
Podが実行できる機能を制限する仕組みです。以前のPod Security Policy(PSP)に代わる標準です。
3つのポリシーレベル
| レベル | 説明 | 用途 |
|---|---|---|
| Privileged | 制限なし | 信頼できるシステムコンポーネント |
| Baseline | 基本的な制限 | ほとんどのアプリケーション |
| Restricted | 厳格な制限 | セキュリティクリティカルなアプリ |
Namespace レベルで適用
# production namespace に Restricted ポリシーを適用
apiVersion: v1
kind: Namespace
metadata:
name: production
labels:
# enforce: ポリシー違反のPodは起動を拒否
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/enforce-version: latest
# audit: 違反をログに記録(起動は許可)
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/audit-version: latest
# warn: 違反を警告表示(起動は許可)
pod-security.kubernetes.io/warn: restricted
pod-security.kubernetes.io/warn-version: latest
Restricted ポリシーに準拠したDeployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: secure-app
namespace: production
spec:
replicas: 3
selector:
matchLabels:
app: secure-app
template:
metadata:
labels:
app: secure-app
spec:
# 1. 非rootユーザーで実行
securityContext:
runAsNonRoot: true
runAsUser: 10001
fsGroup: 10001
seccompProfile:
type: RuntimeDefault
containers:
- name: app
image: myapp:v1.0.0
ports:
- containerPort: 8080
# 2. コンテナレベルのセキュリティ設定
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL # すべてのLinux capabilitiesを削除
# 3. リソース制限
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "200m"
# 4. 書き込み可能な一時ボリューム
volumeMounts:
- name: tmp
mountPath: /tmp
volumes:
- name: tmp
emptyDir: {}
ポリシー違反の検証
# ポリシー違反のPodをデプロイしてみる
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: insecure-pod
namespace: production
spec:
containers:
- name: nginx
image: nginx
EOF
# エラー出力例
Error from server (Forbidden): error when creating "STDIN": pods "insecure-pod" is forbidden:
violates PodSecurity "restricted:latest": allowPrivilegeEscalation != false (container "nginx" must set securityContext.allowPrivilegeEscalation=false),
unrestricted capabilities (container "nginx" must set securityContext.capabilities.drop=["ALL"]),
runAsNonRoot != true (pod or container "nginx" must set securityContext.runAsNonRoot=true),
seccompProfile (pod or container "nginx" must set securityContext.seccompProfile.type to "RuntimeDefault" or "Localhost")
2. RBAC(Role-Based Access Control)
RBACとは?
誰が(User/ServiceAccount)、何を(リソース)、どうできるか(操作)を制御する仕組み
Role と ClusterRole の違い
| 種類 | スコープ | 用途 |
|---|---|---|
| Role | Namespace内 | 特定namespaceのリソース |
| ClusterRole | クラスタ全体 | Nodeなどクラスタリソース |
実装例: 開発チーム用のRole
# Role: production namespace内での読み取り専用権限
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: developer-read-only
namespace: production
rules:
- apiGroups: ["", "apps", "batch"]
resources:
- pods
- pods/log
- deployments
- services
- configmaps
- jobs
verbs:
- get
- list
- watch
---
# RoleBinding: UserをRoleにバインド
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: developer-read-only-binding
namespace: production
subjects:
- kind: User
name: john@example.com
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: developer-read-only
apiGroup: rbac.authorization.k8s.io
ServiceAccount用のRBAC
# ServiceAccount作成
apiVersion: v1
kind: ServiceAccount
metadata:
name: backend-sa
namespace: production
---
# Role: ConfigMapとSecretの読み取り専用
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: config-reader
namespace: production
rules:
- apiGroups: [""]
resources:
- configmaps
- secrets
verbs:
- get
- list
---
# RoleBinding: ServiceAccountをRoleにバインド
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: backend-config-reader-binding
namespace: production
subjects:
- kind: ServiceAccount
name: backend-sa
namespace: production
roleRef:
kind: Role
name: config-reader
apiGroup: rbac.authorization.k8s.io
---
# DeploymentでServiceAccountを使用
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend
namespace: production
spec:
template:
spec:
serviceAccountName: backend-sa # ServiceAccountを指定
containers:
- name: app
image: backend:v1.0.0
RBAC検証
# john@example.com としてログイン
kubectl config use-context john-context
# 読み取りは成功
kubectl get pods -n production
# 作成は失敗
kubectl run test -n production --image=nginx
# Error from server (Forbidden): pods is forbidden: User "john@example.com" cannot create resource "pods" in API group "" in the namespace "production"
ServiceAccountの権限検証
kubectl auth can-i コマンドでServiceAccountの権限を確認できます
# ServiceAccountとしてconfigmapsの取得が可能か確認
$ kubectl auth can-i get configmaps \
--as=system:serviceaccount:production:backend-sa \
-n production
yes
# ServiceAccountとしてdeploymentsの作成が可能か確認
$ kubectl auth can-i create deployments \
--as=system:serviceaccount:production:backend-sa \
-n production
no
# ServiceAccountとしてsecretsの取得が可能か確認
$ kubectl auth can-i get secrets \
--as=system:serviceaccount:production:backend-sa \
-n production
yes
3. Secrets管理
Secretsの課題
- ❌ デフォルトではbase64エンコードのみ(暗号化なし)
- ❌ Gitに直接コミットすると漏洩リスク
- ❌ 複数環境で異なるSecretを管理するのが大変
解決策1: Sealed Secrets(GitOpsフレンドリー)
# Sealed Secrets Controllerインストール
helm repo add sealed-secrets https://bitnami-labs.github.io/sealed-secrets
helm install sealed-secrets sealed-secrets/sealed-secrets -n kube-system
# kubeseal CLIインストール
brew install kubeseal
# 通常のSecretを作成
kubectl create secret generic db-credentials \
--from-literal=username=admin \
--from-literal=password=supersecret \
--dry-run=client -o yaml > secret.yaml
# SealedSecretに変換(暗号化)
kubeseal -f secret.yaml -w sealed-secret.yaml
# SealedSecretをGitに安全にコミット可能
cat sealed-secret.yaml
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
name: db-credentials
namespace: production
spec:
encryptedData:
username: AgBY7Z... # 暗号化されたデータ
password: AgCx9K...
# SealedSecretをクラスタに適用
kubectl apply -f sealed-secret.yaml
# Controllerが自動的にSecretに復号化
kubectl get secret db-credentials -n production -o yaml
解決策2: External Secrets Operator(クラウド統合)
AWS Secrets Manager、Azure Key Vault、GCP Secret Managerと統合
# ExternalSecret: AWS Secrets Managerから取得
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: db-credentials
namespace: production
spec:
refreshInterval: 1h
secretStoreRef:
name: aws-secret-store
kind: SecretStore
target:
name: db-credentials
creationPolicy: Owner
data:
- secretKey: username
remoteRef:
key: production/database
property: username
- secretKey: password
remoteRef:
key: production/database
property: password
4. Network Policy(ネットワーク分離)
Network Policyとは?
Pod間の通信を制御するファイアウォールです。デフォルトでは全Podが相互通信可能なので、Network Policyで制限することで「最小権限の原則」をネットワークレベルで実現します。
CNI依存の注意
NetworkPolicyはKubernetesの標準リソースですが、実際の施行にはCNIのサポートが必要です。
| CNI | NetworkPolicy対応 | 備考 |
|---|---|---|
| Flannel | ❌ 非対応 | ネットワーキングのみ |
| Calico | ✅ 対応 | BGPルーティング対応 |
| Cilium | ✅ 対応 | eBPF、L7ポリシー、gRPC対応 |
📋 本記事の環境について
本記事のクラスタはFlannelを使用しているため、NetworkPolicyの動作確認はできていません。
将来的にCiliumなどgRPC対応のCNIへの移行を予定しています。
NetworkPolicyの詳細実装は別記事で解説予定です。
5. イメージセキュリティ
コンテナイメージのセキュリティは重要ですが、本記事では概要のみ紹介します。
主要ツール
| ツール | 用途 | 参考リンク |
|---|---|---|
| Trivy | イメージ脆弱性スキャン | aquasecurity/trivy |
| Cosign | イメージ署名・検証 | sigstore/cosign |
| Kyverno | ポリシーエンジン(署名検証等) | kyverno.io |
6. 認証基盤(OIDC/LDAP連携)
Kubernetesの認証をより安全にするため、外部認証プロバイダとの連携が推奨されます。
選択肢
| 方式 | 特徴 | 推奨 |
|---|---|---|
| Google OIDC | 手軽、無料 | 個人・小規模 |
| Okta | 開発者無料枠あり | バランス良い |
| Dex + LDAP | 自己完結、柔軟 | 企業・ホームラボ |
メリット
- SSO: 一度のログインで複数クラスタにアクセス
- MFA: 多要素認証でセキュリティ強化
- 監査: 認証イベントの一元管理
- 自動失効: 退職者のアクセス自動無効化
📝 詳細記事
OIDC/LDAP連携の詳細実装は別記事で解説予定です。
7. 監査ログ(Audit Logging)
Audit Policy設定
# /etc/kubernetes/audit-policy.yaml
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
# Secret/ConfigMap/Tokenへのアクセスをログ
- level: RequestResponse
resources:
- group: ""
resources: ["secrets", "configmaps", "serviceaccounts/token"]
# 重要なリソースの変更をログ
- level: RequestResponse
verbs: ["create", "update", "patch", "delete"]
resources:
- group: "apps"
resources: ["deployments", "statefulsets", "daemonsets"]
# 認証・認可の失敗をログ
- level: Metadata
omitStages:
- RequestReceived
API Server設定(Talos Linux)
# talos-config.yaml
machine:
apiServer:
auditPolicy:
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
- level: RequestResponse
resources:
- group: ""
resources: ["secrets"]
まとめ
所感
今回は「セキュリティベストプラクティス」と題しつつも、Cluster層に焦点を絞って実装しました。4Cの全てを網羅しようとすると範囲が広過ぎで後日調べてまとめていきたいと思います
実際に検証してみてそれぞれの目的もはっきりしているが、どのように組み合わせて運用していくのかがまたセキュリティのキモなのかなと想像していますが、昨今のトレンド(?)を追いかけつつ、知識を深めていきたいです
そして蛇足ですが...「Tailscale Extensionを追加しよう」という軽い気持ちが、まさかのクラスタ再構築に発展し、アドベントカレンダー締め切りギリギリになるとは思いませんでした...
次回予告
明日(12/18)は、Chaos Meshでカオスエンジニアリング入門です。本番環境で意図的に障害を起こし、システムの耐障害性を検証します!