Kubernetes セキュリティ: RBAC、ポッド セキュリティ標準、および OPA ゲートキーパー
デフォルトの驚くほど安全でない Kubernetes クラスター: コンテナーは実行可能 rootとして、ホストノードのファイルシステムをマウントし、トークンを使用してサーバーAPIにアクセスします。 広範な権限を持つ ServiceAccount。 2025 年には、環境内のセキュリティ インシデントの 68% が コンテナ化は脆弱性ではなく構成エラーが原因でした ソフトウェア (出典: Sysdig Container Security Report 2025)。良いニュース: Kubernetes は、これらの危険なセットアップを排除する強力なツールを提供します。
この記事では、完全なクラスター強化について説明します。 RBAC 確認する Kubernetes API を使って誰が何をできるのか、 ポッドのセキュリティ標準 防ぐために 特権コンテナと危険な構成、e OPA ゲートキーパー のために 「すべての画像は次の場所から取得する必要がある」などのカスタム企業ポリシーを実装します。 内部レジストリ」または「リソース制限なしの展開なし」。
何を学ぶか
- Kubernetes 認証モデル: RBAC、動詞、リソース、スコープ
- 最小特権の原則: 適用されるRole、ClusterRole、RoleBinding
- ServiceAccount: 自動マウントされるトークンを制限し、IRSA/Workload Identity を使用する方法
- ポッドのセキュリティ標準: 名前空間による特権、ベースライン、制限、および適用
- OPA ゲートキーパー: インストール、Rego の ConstraintTemplate、制約
- 共通ポリシー: 承認されたレジストリ イメージ、必須のリソース制限、ルートなし
- 監査ログ: クラスター内で誰が何をしているかを監視します。
- 完全な硬化チェックリスト
RBAC: ロールベースのアクセス制御
RBAC (Role-Based Access Control) は、Kubernetes の中核となる承認メカニズムです。 定義する 誰が (件名: ユーザー、グループ、サービスアカウント) を実行できます どのような行動 (動詞: 取得、リスト、監視、作成、更新、パッチ、削除) 上へ どのリソース (リソース: ポッド、デプロイメント、シークレット) どの範囲 (Role/RoleBinding を含む名前空間、またはクラスター全体を含む) ClusterRole/ClusterRoleBinding)。
開発チームの役割と役割バインディング
# rbac-developer-role.yaml
# Un ruolo per i developer: possono vedere e modificare
# deployment/pod/service nel loro namespace, ma non secrets
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: developer
namespace: team-alpha
rules:
# Deployment: lettura + scaling
- apiGroups: ["apps"]
resources: ["deployments", "replicasets"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
# Pod: lettura + exec + logs
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["pods/log"]
verbs: ["get", "list"]
- apiGroups: [""]
resources: ["pods/exec"]
verbs: ["create"]
# Service e ConfigMap: accesso completo
- apiGroups: [""]
resources: ["services", "configmaps", "endpoints"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
# HPA
- apiGroups: ["autoscaling"]
resources: ["horizontalpodautoscalers"]
verbs: ["get", "list", "watch"]
# Ingress
- apiGroups: ["networking.k8s.io"]
resources: ["ingresses"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: team-alpha-developers
namespace: team-alpha
subjects:
# Gruppo di utenti (gestito da OIDC/SSO)
- kind: Group
name: "team-alpha-devs"
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: developer
apiGroup: rbac.authorization.k8s.io
スコープが制限された SRE/Admin 用の ClusterRole
# rbac-sre-clusterrole.yaml
# SRE: accesso in lettura a tutto il cluster, write solo su namespace specifici
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: cluster-reader
rules:
- apiGroups: ["*"]
resources: ["*"]
verbs: ["get", "list", "watch"]
# Nega esplicita: non puo leggere i secrets (sovrascritta da DENY)
# NOTA: in RBAC Kubernetes non esiste DENY esplicita - usa Gatekeeper per questo
---
# SRE: read su tutto + write limitato ai namespace di produzione
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: sre-cluster-reader
subjects:
- kind: Group
name: "platform-sre"
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: cluster-reader
apiGroup: rbac.authorization.k8s.io
# Verifica i permessi di un utente/serviceaccount
kubectl auth can-i create pods --as=system:serviceaccount:production:api-server-sa
kubectl auth can-i delete secrets --as=developer-user -n production
kubectl auth can-i '*' '*' --as=alice # lista tutto quello che alice puo fare
ServiceAccount: 自動トークンの制限
デフォルトでは、各ポッドは有効な ServiceAccount トークンを自動的にマウントします。ポッドでは、 彼らは Kubernetes API を呼び出しません。このトークンは役に立ちませんが、攻撃対象領域が増加します。
# serviceaccount-minimal.yaml
# ServiceAccount dedicata per ogni applicazione (mai usare default)
apiVersion: v1
kind: ServiceAccount
metadata:
name: api-server-sa
namespace: production
annotations:
# Su AWS EKS: delega i permessi IAM tramite IRSA
eks.amazonaws.com/role-arn: "arn:aws:iam::123456789:role/ApiServerRole"
automountServiceAccountToken: false # non montare il token automaticamente
---
# Deployment che usa la ServiceAccount
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-server
namespace: production
spec:
template:
spec:
serviceAccountName: api-server-sa
automountServiceAccountToken: false # ridondante ma esplicito
containers:
- name: api-server
image: my-registry/api-server:1.2.0
---
# Se il Pod deve chiamare l'API K8s, usa un token proiettato con scadenza
# invece del token auto-mounted (che non scade mai)
apiVersion: v1
kind: Pod
spec:
serviceAccountName: api-server-sa
volumes:
- name: api-token
projected:
sources:
- serviceAccountToken:
path: token
expirationSeconds: 3600 # scade ogni ora
audience: kubernetes.default.svc
containers:
- name: api-server
volumeMounts:
- name: api-token
mountPath: /var/run/secrets/kubernetes.io/serviceaccount
readOnly: true
ポッドセキュリティ標準 (PSS)
ポッド セキュリティ標準は、非推奨の PodSecurityPolicies (K8s 1.25 で削除) を置き換えます。 これらは、ラベルを介して名前空間レベルで適用できる 3 つのセキュリティ レベルを定義します。
- 特権: 制限なし (kube-system などのシステム名前空間のみ)
- ベースライン: 最も悪名高くリスクの高い構成 (特権コンテナ、hostPath、hostNetwork) を防止します。
- 制限付き: セキュリティを最大限に高めるための現在のベスト プラクティス (非 root、seccomp、機能の低下)
各レベルには 3 つのモードがあります。 enforce (ポッドをブロックします)、 audit
(ログは記録しますがブロックはしません)、 warn (ユーザーに警告を表示します)。
ポッドセキュリティ標準を名前空間に適用する
# Applica PSS al namespace tramite label
kubectl label namespace production \
pod-security.kubernetes.io/enforce=restricted \
pod-security.kubernetes.io/enforce-version=latest \
pod-security.kubernetes.io/audit=restricted \
pod-security.kubernetes.io/audit-version=latest \
pod-security.kubernetes.io/warn=restricted \
pod-security.kubernetes.io/warn-version=latest
# Per namespace di sistema che richiedono pod privilegiati
kubectl label namespace kube-system \
pod-security.kubernetes.io/enforce=privileged
# Testa cosa succederebbe se applicassi restricted a un namespace esistente
kubectl label --dry-run=server --overwrite namespace production \
pod-security.kubernetes.io/enforce=restricted
制限レベルに準拠したポッド
# pod-restricted-compliant.yaml
# Un Pod che rispetta tutte le regole del livello Restricted
apiVersion: v1
kind: Pod
metadata:
name: secure-pod
namespace: production
spec:
securityContext:
runAsNonRoot: true # non girare come root
runAsUser: 1000 # UID non-root
runAsGroup: 3000
fsGroup: 2000
seccompProfile:
type: RuntimeDefault # profilo seccomp di default
supplementalGroups: [1000]
containers:
- name: app
image: my-registry/api-server:1.2.0
securityContext:
allowPrivilegeEscalation: false # fondamentale: previene sudo
readOnlyRootFilesystem: true # filesystem immutabile
capabilities:
drop:
- ALL # rimuovi tutte le Linux capabilities
# add: [] - non aggiungere nessuna capability
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "512Mi"
volumeMounts:
- name: tmp
mountPath: /tmp # se l'app scrive su /tmp, monta un volume
- name: cache
mountPath: /app/cache
volumes:
- name: tmp
emptyDir: {}
- name: cache
emptyDir: {}
automountServiceAccountToken: false
OPA Gatekeeper: Kubernetes 用のポリシー エンジン
ポッド セキュリティ標準は、固定された一連のコントロールをカバーしています。 OPA ゲートキーパーが許可する を使用してカスタム ポリシーを定義するには レゴ、OPAの言語。 API サーバーへのすべてのリクエストをインターセプトするアドミッション Webhook として実装されます。 定義されたポリシーに対して有効です。
OPA ゲートキーパーのインストール
# Installa Gatekeeper con Helm
helm repo add gatekeeper https://open-policy-agent.github.io/gatekeeper/charts
helm repo update
helm install gatekeeper gatekeeper/gatekeeper \
--namespace gatekeeper-system \
--create-namespace \
--version 3.17.0 \
--set auditInterval=30 \
--set constraintViolationsLimit=100 \
--set logLevel=INFO
# Verifica installazione
kubectl get pods -n gatekeeper-system
ConstraintTemplate: 承認されたレジストリからのイメージのみ
# constraint-template-registry.yaml
# Il template definisce lo schema e la logica in Rego
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8sallowedrepos
annotations:
description: "Richiede che le immagini provengano solo da registry approvati"
spec:
crd:
spec:
names:
kind: K8sAllowedRepos
validation:
openAPIV3Schema:
type: object
properties:
repos:
type: array
items:
type: string
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8sallowedrepos
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
not starts_with_allowed(container.image)
msg := sprintf("Il container '%v' usa l'immagine '%v' che non proviene da un registry approvato", [container.name, container.image])
}
violation[{"msg": msg}] {
container := input.review.object.spec.initContainers[_]
not starts_with_allowed(container.image)
msg := sprintf("L'initContainer '%v' usa l'immagine '%v' non approvata", [container.name, container.image])
}
starts_with_allowed(image) {
repo := input.parameters.repos[_]
startswith(image, repo)
}
---
# Constraint: applica la policy con i parametri specifici
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sAllowedRepos
metadata:
name: require-approved-registry
spec:
enforcementAction: deny # deny|warn|dryrun
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
excludedNamespaces:
- kube-system
- gatekeeper-system
- monitoring
parameters:
repos:
- "registry.company.internal/"
- "gcr.io/company-project/"
- "public.ecr.aws/company/"
ConstraintTemplate: 必須のリソース制限
# constraint-template-resource-limits.yaml
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8srequiredresources
spec:
crd:
spec:
names:
kind: K8sRequiredResources
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8srequiredresources
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
not container.resources.limits.cpu
msg := sprintf("Container '%v': cpu limit obbligatorio ma mancante", [container.name])
}
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
not container.resources.limits.memory
msg := sprintf("Container '%v': memory limit obbligatorio ma mancante", [container.name])
}
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
not container.resources.requests.cpu
msg := sprintf("Container '%v': cpu request obbligatoria ma mancante", [container.name])
}
---
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredResources
metadata:
name: require-resource-limits
spec:
enforcementAction: deny
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
excludedNamespaces:
- kube-system
- gatekeeper-system
ConstraintTemplate: コンテナ ルートなし
# constraint-template-no-root.yaml
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8spsphostnamespace
spec:
crd:
spec:
names:
kind: K8sPSPHostNamespace
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8spsphostnamespace
violation[{"msg": msg}] {
input.review.object.spec.hostPID == true
msg := "hostPID non e consentito"
}
violation[{"msg": msg}] {
input.review.object.spec.hostIPC == true
msg := "hostIPC non e consentito"
}
violation[{"msg": msg}] {
input.review.object.spec.hostNetwork == true
not input.review.object.metadata.annotations["policy.company.internal/exempt-hostnetwork"]
msg := "hostNetwork non e consentito senza annotation di esenzione"
}
# Verifica violazioni esistenti nel cluster
kubectl get constraints
kubectl describe k8srequiredresources require-resource-limits
# Nella sezione Status.Violations vedrai tutti i Pod non conformi
監査ログ
Kubernetes は、API サーバーへのすべてのリクエストを構造化された監査ログに記録できます。 コンプライアンスやインシデント調査には不可欠です。
# audit-policy.yaml - configurazione dell'audit log
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
# Registra tutto sui Secrets a livello RequestResponse (incluso il contenuto)
- level: RequestResponse
resources:
- group: ""
resources: ["secrets"]
# Registra le modifiche a RBAC
- level: RequestResponse
resources:
- group: "rbac.authorization.k8s.io"
resources: ["roles", "clusterroles", "rolebindings", "clusterrolebindings"]
# Registra exec nei Pod (potenziale accesso malevolo)
- level: Request
resources:
- group: ""
resources: ["pods/exec", "pods/portforward", "pods/proxy"]
# Per tutto il resto: registra solo i metadata (chi ha fatto cosa, quando)
- level: Metadata
omitStages:
- RequestReceived
# Kube-apiserver config (aggiungi all'avvio di kube-apiserver):
# --audit-log-path=/var/log/kubernetes/audit.log
# --audit-log-maxage=30
# --audit-log-maxbackup=10
# --audit-log-maxsize=100
# --audit-policy-file=/etc/kubernetes/audit-policy.yaml
Kubernetes の強化チェックリスト
セキュリティ強化チェックリスト
- RBAC: 各 ServiceAccount およびユーザー グループの最小権限の原則
- サービスアカウント:
automountServiceAccountToken: falseサーバー API を呼び出さないすべての Pod 上 - トークンの有効期限: で投影されたトークンを使用する
expirationSeconds永久トークンの代わりに - ポッドのセキュリティ標準:
restrictedすべての実稼働名前空間にわたって、baselineステージング上で - ルートがありません:
runAsNonRoot: trueeallowPrivilegeEscalation: falseすべてのコンテナに - 不変ファイルシステム:
readOnlyRootFilesystem: true+ /tmp および /cache のボリューム emptyDir - リソース制限: すべてのコンテナの CPU とメモリの制限 (強制のためのゲートキーパー)
- 承認されたレジストリ: 未承認のレジストリからのイメージをブロックするゲートキーパー制約
- ネットワークポリシー: すべての実稼働名前空間でのデフォルト拒否 (記事 1 を参照)
- 秘密: Secret Kubernetes プレーンの代わりに外部シークレット マネージャー (AWS Secrets Manager、Vault) を使用する
- 監査ログ: SIEM またはログ アグリゲータで有効化および一元化
- etcd: 暗号化構成で保存時に暗号化される
- サーバー API: 匿名アクセスは無効、アドミッション コントローラは有効
よくあるセキュリティ上の間違い
- ワークロードのデフォルトの名前空間: デフォルトの名前空間には、デフォルトで寛容な RBAC が設定されています。ワークロードには常に専用の名前空間を使用してください
- CI/CD パイプライン用の ClusterAdmin: GitOps パイプラインにはクラスター管理者は必要ありません。ターゲットの名前空間に必要な最小限の権限を持つ ServiceAccount を作成します。
- 環境変数としてのシークレット: 環境変数は次の場所に表示されます
kubectl describe podそしてログに。 Secret または外部 Secret Manager によってマウントされたファイルを使用する - 画像の最新のタグ: 最新のタグは変更される可能性があります。再現性とセキュリティを確保するために、常に SHA256 ダイジェストまたは不変タグを使用してください。
- 基本イメージの CVE を無視します。 Trivy、Snyk、または Grype を使用して画像を定期的にスキャンします。クリティカルな CVE を含む Ubuntu ベース イメージにより、クラスターの強化作業がキャンセルされる
結論と次のステップ
Kubernetes クラスターのセキュリティは階層化されたプロセスです: RBAC がアクセスを制御します API に接続すると、ポッド セキュリティ標準によりポッド レベルでの危険な構成が防止されます。 OPA Gatekeeper を使用すると、企業ポリシーをバージョン付きコードとしてエンコードでき、 監査可能。これらのツールだけでは十分ではありません。一緒にそれらは一つを形成します 多層防御により、攻撃対象領域が大幅に減少します。
さらなるセキュリティ強化とシステム導入への次のステップ コンテナ システム コールをリアルタイムで監視する Falco などのランタイム セキュリティ ツール 異常な動作を警告します (本番環境のコンテナでシェルが開かれ、読み取りが行われます) 資格情報ファイル、予期しないネットワーク接続など)。
Kubernetes at Scale シリーズの今後の記事
以前の記事
関連シリーズ
- DevSecOps — コードとしてのポリシー、CI/CD パイプラインでのセキュリティ スキャン
- Kubernetes ネットワーキング — ワークロードを分離するためのネットワーク ポリシー
- プラットフォームエンジニアリング — チーム用の自動ガードレールなどのセキュリティ







