Bezpieczeństwo Kubernetes: RBAC, standardy bezpieczeństwa podów i strażnik OPA
Domyślny i zaskakująco niepewny klaster Kubernetes: można uruchamiać kontenery jako root, zamontuj system plików węzła hosta, uzyskaj dostęp do API serwera za pomocą tokena ServiceAccount z szerokimi uprawnieniami. W 2025 r. 68% incydentów bezpieczeństwa w środowiskach Containerized można przypisać błędom konfiguracji, a nie lukom w zabezpieczeniach oprogramowanie (źródło: Sysdig Container Security Report 2025). Dobra wiadomość: Kubernetes oferuje potężne narzędzia eliminujące te ryzykowne konfiguracje.
W tym artykule opisano pełne wzmocnienie klastra: RBAC sprawdzić kto co może zrobić z Kubernetes API, Standardy bezpieczeństwa kapsuł zapobiegać uprzywilejowane kontenery i ryzykowne konfiguracje, np Strażnik OPA dla wdrażaj niestandardowe zasady firmy, takie jak „wszystkie obrazy muszą pochodzić rejestr wewnętrzny” lub „brak wdrażania bez limitów zasobów”.
Czego się nauczysz
- Model autoryzacji Kubernetes: RBAC, czasowniki, zasoby, zakres
- Zasada najmniejszych uprawnień: zastosowana rola, ClusterRole, RoleBinding
- ServiceAccount: Jak ograniczyć automatycznie montowane tokeny i używać IRSA/Workload Identity
- Standardy zabezpieczeń podów: uprzywilejowane, podstawowe, ograniczone i egzekwowane według przestrzeni nazw
- Gatekeeper OPA: Instalacja, ConstraintTemplate w Rego, Constraint
- Wspólne zasady: zatwierdzone obrazy rejestru, obowiązkowe limity zasobów, brak uprawnień roota
- Rejestrowanie inspekcji: Monitoruj, kto co robi w klastrze
- Pełna lista kontrolna hartowania
RBAC: Kontrola dostępu oparta na rolach
RBAC (kontrola dostępu oparta na rolach) to podstawowy mechanizm autoryzacji Kubernetes. Definiuje Kto (Temat: Użytkownik, Grupa, Konto Serwisowe) może zostać wykonane jaka akcja (czasownik: pobrać, wyświetlić, obejrzeć, utworzyć, zaktualizować, załatać, usunąć) w górę jakie źródło (Zasób: zasobniki, wdrożenia, sekrety) w jaki zakres (przestrzeń nazw z Rolą/RoleBinding lub cały klaster z ClusterRole/ClusterRoleBinding).
Rola i wiązanie ról dla zespołu programistów
# 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
Rola klastra dla SRE/administratora z ograniczonym zakresem
# 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: Limit automatycznych tokenów
Domyślnie każdy Pod automatycznie montuje ważny token ServiceAccount. W kapsułach to nie wywołują API Kubernetes, ten token jest bezużyteczny, ale zwiększa powierzchnię ataku:
# 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
Standardy bezpieczeństwa kapsuł (PSS)
Standardy bezpieczeństwa pod zastępują przestarzałe zasady PodSecurityPolicies (usunięte w K8s 1.25). Definiują trzy poziomy bezpieczeństwa obowiązujące na poziomie przestrzeni nazw poprzez etykiety:
- Uprzywilejowany: brak ograniczeń (tylko dla systemowych przestrzeni nazw, takich jak kube-system)
- Wartości bazowe: zapobiega najbardziej znanym i ryzykownym konfiguracjom (kontenery uprzywilejowane, hostPath, hostNetwork)
- Ograniczony: Aktualne najlepsze praktyki zapewniające maksymalne bezpieczeństwo (inne niż root, seccomp, spadek możliwości)
Każdy poziom ma trzy tryby: enforce (blokuje Pod), audit
(loguje, ale nie blokuje), warn (pokazuje ostrzeżenie dla użytkownika).
Zastosuj standardy bezpieczeństwa podów do przestrzeni nazw
# 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 zgodny z ograniczonym poziomem
# 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: silnik zasad dla Kubernetes
Standardy bezpieczeństwa kapsuł obejmują ustalony zestaw kontroli. OPA Gatekeeper pozwala aby zdefiniować niestandardowe zasady za pomocą Rego, język OPA. Jest zaimplementowany jako Admission Webhook, który przechwytuje każde żądanie kierowane do serwera API i ważne w odniesieniu do określonych zasad.
Instalacja OPA Gatekeepera
# 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: obrazy tylko z zatwierdzonego rejestru
# 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: Obowiązkowe limity zasobów
# 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: Brak katalogu głównego kontenera
# 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
Rejestrowanie audytu
Kubernetes może rejestrować każde żądanie kierowane do serwera API w ustrukturyzowanym dzienniku audytu. Jest to niezbędne do zapewnienia zgodności i dochodzenia w sprawie incydentów.
# 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
Lista kontrolna hartowania Kubernetesa
Lista kontrolna wzmacniania zabezpieczeń
- RBAC: Zasada najmniejszych uprawnień dla każdego konta usługi i grupy użytkowników
- Konto serwisowe:
automountServiceAccountToken: falsena wszystkich Podach, które nie wywołują API serwera - Wygaśnięcie tokena: użyj wyświetlanych tokenów
expirationSecondszamiast stałych tokenów - Standardy bezpieczeństwa kapsuł:
restrictedwe wszystkich produkcyjnych przestrzeniach nazw,baselinena inscenizacji - Brak roota:
runAsNonRoot: trueeallowPrivilegeEscalation: falsena wszystkich kontenerach - Niezmienny system plików:
readOnlyRootFilesystem: true+ wolumin pustyKatalog dla /tmp i /cache - Limity zasobów: Limity procesora i pamięci we wszystkich kontenerach (Gatekeeper w celu egzekwowania)
- Zatwierdzone rejestry: Ograniczenie strażnika, które blokuje obrazy z nieautoryzowanych rejestrów
- Polityka sieciowa: default-deny we wszystkich produkcyjnych przestrzeniach nazw (patrz artykuł 1)
- Tajniki: użyj zewnętrznego menedżera sekretów (AWS Secrets Manager, Vault) zamiast zwykłego Secret Kubernetes
- Dzienniki audytu: włączone i scentralizowane w SIEM lub agregatorze logów
- itd.: zaszyfrowane w stanie spoczynku z konfiguracją szyfrowania
- API serwera: dostęp anonimowy wyłączony, kontrolery dostępu włączone
Typowe błędy bezpieczeństwa
- Domyślna przestrzeń nazw dla obciążeń: domyślna przestrzeń nazw ma domyślnie zezwalającą kontrolę RBAC; Zawsze używaj dedykowanych przestrzeni nazw dla swoich obciążeń
- ClusterAdmin dla potoków CI/CD: Potoki GitOps nie wymagają administratora klastra; Utwórz konto ServiceAccount z minimalnymi uprawnieniami wymaganymi dla docelowej przestrzeni nazw
- Sekret jako zmienne środowiskowe: zmienne środowiskowe są widoczne w
kubectl describe podoraz w logach; używaj plików zamontowanych przez sekrety lub zewnętrznych menedżerów sekretów - Najnowsze tagi na obrazach: najnowszy tag może ulec zmianie; Aby zapewnić powtarzalność i bezpieczeństwo, zawsze używaj skrótu SHA256 lub znaczników niezmiennych
- Ignoruj obrazy CVE obrazu podstawowego: regularnie skanuj obrazy za pomocą Trivy, Snyk lub Grype; podstawowy obraz Ubuntu z krytycznymi CVE anuluje prace wzmacniające klaster
Wnioski i dalsze kroki
Bezpieczeństwo klastra Kubernetes to proces wielowarstwowy: kontrola dostępu przez kontrolę RBAC do API, Standardy Bezpieczeństwa Podu zapobiegają ryzykownym konfiguracjom na poziomie Poda, a OPA Gatekeeper pozwala kodować zasady firmy jako kod wersjonowany i kontrolowane. Żadne z tych narzędzi samo w sobie nie jest wystarczające; razem tworzą jedno głęboka obrona, która drastycznie zmniejsza powierzchnię ataku.
Kolejny krok w kierunku dalszego wzmocnienia bezpieczeństwa i wdrożenia systemu narzędzia zabezpieczające środowisko uruchomieniowe, takie jak Falco, które monitorują wywołania systemowe kontenerów w czasie rzeczywistym i ostrzegaj o nietypowym zachowaniu (powłoka otwarta w kontenerze w produkcji, czytanie plików uwierzytelniających, nieoczekiwanych połączeń sieciowych).
Nadchodzące artykuły z serii Kubernetes at Scale
Poprzednie artykuły
Powiązane serie
- DevSecOps — polityka jako kod, skanowanie bezpieczeństwa w potokach CI/CD
- Sieć Kubernetesa — Zasady sieciowe do izolowania obciążeń
- Inżynieria Platformy — bezpieczeństwo, takie jak automatyczne poręcze dla drużyn







