Vanilla Kubernetes

Drugi deo: Workload platforma, mreža, podaci i bezbednost
SecurityContext, Pod Security Standards i admission – sprečavanje privilegovanih workload-a
VIII. Identitet, autorizacija i zaštita workload-a
37. SecurityContext, Pod Security Standards i admission – sprečavanje privilegovanih workload-a

Prethodne lekcije razdvojile su identitet workload-a od njegovih dozvola: ServiceAccount određuje ko šalje API zahtev, a RBAC šta taj identitet sme da uradi. Međutim, ni precizan RBAC ne sprečava proces u kontejneru da bude pokrenut kao korisnik root, zatraži opasne Linux capabilities, koristi host mrežu, montira putanju čvora ili pokuša eskalaciju privilegija. Ova lekcija uvodi sledeću granicu odbrane: deklarativna podešavanja procesa u SecurityContext objektu i admission kontrolu koja neprihvatljiv Pod odbija pre nego što stigne do kubelet-a.

Bezbedan workload nije rezultat jednog polja. Slika mora biti izgrađena za rad bez root privilegija, manifest mora eksplicitno smanjiti ovlašćenja, runtime i kernel moraju podržavati izabrane mehanizme, a API server mora dosledno sprovoditi organizacionu politiku. Kubernetes Pod Security Standards (PSS) definišu tri verzionisana profila, dok ugrađeni Pod Security Admission (PSA) primenjuje te profile pri prijemu zahteva. Ovi mehanizmi smanjuju rizik, ali ne zamenjuju bezbednu sliku, RBAC, NetworkPolicy, zaštitu tajni, skeniranje ranjivosti, runtime detekciju ni audit.

SecurityContext kao ugovor o izvršavanju procesa

securityContext opisuje privilegije i kontrole pristupa sa kojima se proces izvršava. Polje postoji na nivou poda kao PodSecurityContext i na nivou pojedinačnog kontejnera kao SecurityContext. Podešavanja poda, kao što su runAsUser, runAsGroup, fsGroup, supplementalGroups, SELinux opcije i pod-level seccomp profil, mogu postaviti zajedničku osnovu. Kontejnerski nivo sadrži i kontrole kao što su privileged, allowPrivilegeEscalation, capabilities i readOnlyRootFilesystem; kada isto podržano podešavanje postoji na oba nivoa, eksplicitna vrednost kontejnera ima prednost za taj kontejner.

Init i ephemeral kontejneri su deo iste granice poverenja. Administrator koji proveri samo spec.containers može prevideti privilegovani init kontejner koji menja volume pre pokretanja aplikacije ili ephemeral kontejner dodat tokom dijagnostike. PSS zato relevantne kontrole primenjuje na regularne, init i ephemeral kontejnere. Sidecar implementiran kao restartabilni init kontejner takođe se proverava kao init kontejner.

Podešavanje Namena i posledica Važna granica
runAsNonRoot: true Kubelet odbija pokretanje kada utvrdi da bi proces radio kao UID 0. Ako slika koristi simboličkog korisnika čiji UID runtime ne može potvrditi, pokretanje može biti odbijeno; numerički ne-root UID daje proverljiviji ugovor.
runAsUser i runAsGroup Postavljaju numerički UID i primarni GID procesa. Nasumičan ili fiksan UID mora imati pristup aplikacionim fajlovima i volume-u; pogrešne dozvole izazivaju Permission denied.
allowPrivilegeEscalation: false Postavlja Linux no_new_privs i sprečava sticanje većih privilegija preko setuid/setgid binarnih fajlova. Efektivno je uvek true za privilegovani kontejner ili kontejner sa CAP_SYS_ADMIN; ne znači da proces nema postojeće capabilities.
privileged: true Uklanja veliki deo izolacije i daje kontejneru privilegije približne procesu na hostu. Ne koristi se za poslovne aplikacije; opravdani infrastrukturni izuzetak zahteva odvojenu zonu poverenja i dodatne kontrole.
capabilities Razlaže tradicionalne root privilegije na manje Linux jedinice; preporučeno je drop: ["ALL"] pa dodavanje samo dokazane potrebe. Capability je svojstvo Linux kernela. Dodela NET_ADMIN, SYS_ADMIN, SYS_PTRACE ili sličnih moćnih prava zahteva posebnu analizu.
seccompProfile Filtrira sistemske pozive; RuntimeDefault koristi podrazumevani profil container runtime-a, a Localhost profil sa čvora. RuntimeDefault se može razlikovati između runtime-a i verzija; Localhost profil mora postojati na svakom mogućem čvoru.
readOnlyRootFilesystem: true Sprečava upis u sloj korenskog filesystem-a kontejnera i otežava trajno menjanje binarnih fajlova. Aplikacija mora dobiti namenski writable volume za privremene fajlove, keš ili PID; PSS Restricted ne zahteva ovo polje.
fsGroup i fsGroupChangePolicy Omogućavaju grupni pristup podržanim volume-ima; politika OnRootMismatch može izbeći nepotrebnu rekurzivnu promenu vlasništva. Promena vlasništva velikog volume-a može usporiti start; ponašanje zavisi od volume tipa i CSI drajvera.

Linux capabilities, seccomp, AppArmor i SELinux

Ovi mehanizmi rešavaju različite probleme i najbolje rade slojevito. Capabilities određuju koje privilegovane kernel operacije proces može da izvrši. Seccomp ograničava skup sistemskih poziva. AppArmor i SELinux predstavljaju Linux Security Module kontrole koje prema profilu ili oznakama ograničavaju pristup fajlovima, procesima i drugim objektima. Nijedan sloj samostalno ne predstavlja potpun sandbox.

PSS Restricted za Linux zahteva eksplicitni seccomp profil RuntimeDefault ili Localhost, zabranjuje Unconfined, zahteva uklanjanje capability-ja ALL i dozvoljava vraćanje samo NET_BIND_SERVICE. Aktuelni PSS prihvata odgovarajuće AppArmor vrednosti, ali stvarno sprovođenje zavisi od podrške čvora. Od Kubernetes 1.30 AppArmor se zadaje poljem securityContext.appArmorProfile; stara anotacija container.apparmor.security.beta.kubernetes.io/* je deprecated. Za seccomp se koriste strukturisana polja; istorijske alpha seccomp anotacije nisu funkcionalne u podržanim izdanjima.

SELinux oznake se ne izmišljaju u aplikacionom timu. Dozvoljene vrednosti i kompatibilnost zavise od OS-a, runtime-a i politike na čvorovima. PSS ograničava tipove koji se mogu navesti, ali admission prihvatanje ne dokazuje da je konkretan profil učitan niti da će aplikacija imati potreban pristup. Posle admission validacije mora se proveriti stvarno pokretanje procesa i kernel/runtime poruke na čvoru.

Bezbedna osnova manifesta

Sledeći fragment predstavlja produkcionu početnu tačku za tipičnu Linux aplikaciju bez potrebe za Kubernetes API-jem. Ne može se slepo dodati svakoj slici: aplikacija mora podržavati ne-root UID, upis samo u deklarisani emptyDir i rad bez dodatnih capabilities.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: hardened-web
  namespace: secure-apps
spec:
  replicas: 2
  selector:
    matchLabels:
      app: hardened-web
  template:
    metadata:
      labels:
        app: hardened-web
    spec:
      automountServiceAccountToken: false
      securityContext:
        runAsNonRoot: true
        runAsUser: 10001
        runAsGroup: 10001
        fsGroup: 10001
        seccompProfile:
          type: RuntimeDefault
      containers:
        - name: web
          image: registry.example.invalid/platform/hardened-web@sha256:REPLACE_ME
          securityContext:
            allowPrivilegeEscalation: false
            readOnlyRootFilesystem: true
            capabilities:
              drop:
                - ALL
          volumeMounts:
            - name: tmp
              mountPath: /tmp
          resources:
            requests:
              cpu: 50m
              memory: 64Mi
            limits:
              cpu: 500m
              memory: 256Mi
      volumes:
        - name: tmp
          emptyDir:
            sizeLimit: 128Mi

Digest je namerno ostavljen kao obavezna zamena, jer izmišljeni digest ne bi bio izvršiv primer. readOnlyRootFilesystem ne čini emptyDir read-only; proces i dalje može pisati u eksplicitno montiranu putanju. sizeLimit pomaže u kontroli lokalne ephemeral storage potrošnje, ali raspoloživost zavisi i od ukupnog prostora čvora i odgovarajućih resource ograničenja. Readiness/liveness probe, mrežna politika i Service dodaju se prema aplikaciji i ne pripadaju samom PSS profilu.

Pod Security Standards: Privileged, Baseline i Restricted

PSS nisu Kubernetes API objekti niti proizvoljne politike koje administrator uređuje. To su upstream definisani, kumulativni i verzionisani profili. Privileged je odsustvo ograničenja i namenjen je pouzdanim infrastrukturnim workload-ima. Baseline sprečava poznate eskalacije uz visoku kompatibilnost. Restricted primenjuje aktuelne hardening prakse uz cenu veće verovatnoće da starija ili loše pripremljena aplikacija neće proći.

Oblast Baseline Dodatni zahtev Restricted profila
Privilegovani i host pristup Zabranjuje privileged kontejnere, HostProcess, host PID/IPC, hostNetwork i nedozvoljene hostPort vrednosti. Nasleđuje Baseline.
Volume tipovi Zabranjuje hostPath i druge poznato rizične obrasce kroz svoja pravila. Dozvoljava samo eksplicitni skup: ConfigMap, CSI, downwardAPI, emptyDir, ephemeral, PVC, projected i Secret.
Identitet procesa Ne zahteva obavezno non-root izvršavanje. Zahteva runAsNonRoot: true i ne dozvoljava eksplicitni UID 0.
Eskalacija privilegija Zabranjuje poznate opasne kombinacije, ali ne zahteva svako Restricted polje. Za Linux zahteva allowPrivilegeEscalation: false.
Capabilities Zabranjuje dodavanje definisanog skupa opasnih capabilities. Zahteva drop: ["ALL"]; sme se dodati samo NET_BIND_SERVICE.
Seccomp Ne dozvoljava eksplicitni Unconfined, ali profil može biti izostavljen. Za Linux zahteva RuntimeDefault ili Localhost.
Sysctl Dozvoljava samo verzijski definisan skup safe sysctl vrednosti. Nasleđuje Baseline.

Tabela je operativni pregled, ne zamena za zvaničnu verzionisanu specifikaciju. Dopušteni sysctl parametri, SELinux tipovi i druge pojedinosti menjali su se kroz minor verzije. Zato produkciona promena mora dokumentovati i nivo i verziju profila. Vrednost latest automatski usvaja buduću specifikaciju posle nadogradnje control plane-a, što je korisno za kontinuirani audit, ali može neočekivano promeniti enforce odluke.

Šta PSS namerno ne rešava

PSS je široko primenljiva osnovna zaštita, ne kompletna organizaciona politika. Restricted profil, na primer, ne zahteva readOnlyRootFilesystem: true, ne bira dozvoljene image registre ili digest-e, ne ograničava ServiceAccount, ne zahteva resource requests/limits, ne proverava potpis slike, ne definiše egress i ne sprečava aplikacionu ranjivost. PSS takođe ne može izraziti pravilo „samo ovaj konkretni CSI drajver” ili „samo UID iz opsega dodeljenog tenant-u”.

Za dodatna pravila koriste se ValidatingAdmissionPolicy zasnovan na CEL-u ili pažljivo projektovani validating/mutating webhook-ovi. Mutacija može postaviti bezbedne podrazumevane vrednosti, ali ne sme skrivati činjenicu da aplikacioni manifest nije usaglašen. Svaki webhook uvodi zavisnost u API putanju, TLS sertifikate, dostupnost i failure policy; pogrešna politika može blokirati sav deployment ili, uz failurePolicy: Ignore, propustiti kontrolu upravo tokom kvara.

Admission tok i mesto Pod Security kontrole

Posle autentikacije i autorizacije API zahtev prolazi kroz mutating admission, zatim validaciju objekta i validating admission pre upisa u etcd. Admission ne autorizuje korisnika: RBAC može dozvoliti kreiranje poda, a PodSecurity ga ipak odbiti jer je manifest rizičan. Obrnuto, usaglašen SecurityContext ne daje pravo korisniku da kreira workload.

Ugrađeni PodSecurity je validating admission kontroler, stabilan od Kubernetes 1.25 i u aktuelnom Kubernetes 1.36 uključen u podrazumevani skup admission pluginova. Nasledio je uklonjeni PodSecurityPolicy (PSP), ali nije njegov API-kompatibilan naslednik: ne bira između proizvoljnih policy objekata, ne mutira pod da bi popunio security defaults i ne vezuje politiku za korisnika preko RBAC-a. Migracija sa PSP-a zato zahteva analizu stvarnih workload-a, dodavanje eksplicitnih polja i odabir namespace strategije, a ne samo promenu naziva plugina.

Deployment, StatefulSet, DaemonSet ili Job obično samo čuvaju Pod template; njihov kontroler kasnije kreira stvarni Pod. PSA primenjuje enforce na zahtev za Pod, ne na samu kreaciju workload kontrolera. Režimi warn i audit proveravaju Pod template podržanih workload resursa kako bi korisnik dobio raniji signal, ali konačna enforce odluka nastaje kada kontroler pokuša da kreira Pod. Zbog toga je moguće da Deployment objekat bude kreiran, a da nema dostupne replike jer su njegovi Pod zahtevi odbijeni.

Namespace oznake: enforce, audit i warn

PSA politiku najčešće dobija iz standardnih labela namespace-a. Svaki režim ima labelu nivoa i opcionu labelu verzije. Režimi mogu koristiti različite profile: razuman migracioni obrazac je enforce=baseline, dok warn i audit proveravaju restricted. Tako se poznate eskalacije odmah blokiraju, a tim dobija dokaz o preprekama za stroži profil.

apiVersion: v1
kind: Namespace
metadata:
  name: secure-apps
  labels:
    pod-security.kubernetes.io/enforce: baseline
    pod-security.kubernetes.io/enforce-version: v1.36
    pod-security.kubernetes.io/audit: restricted
    pod-security.kubernetes.io/audit-version: v1.36
    pod-security.kubernetes.io/warn: restricted
    pod-security.kubernetes.io/warn-version: v1.36

enforce odbija Pod koji krši profil. warn vraća upozorenje klijentu, ali dozvoljava zahtev. audit dodaje anotaciju audit događaju, pod uslovom da je Kubernetes audit konfigurisan tako da događaj bude zabeležen. Warn nije trajni inventar, a audit bez odgovarajuće audit politike i centralnog prikupljanja nije operativan dokaz.

Namespace labela je bezbednosna kontrola. Pravo menjanja Namespace objekata ili ovih labela praktično omogućava ublažavanje politike, pa mora biti ograničeno RBAC-om i praćeno auditom. Labela se ne kopira automatski na druge namespace-e, a promena globalnog default-a ne mora biti vidljiva u njihovim metapodacima. Inventar zato mora razlikovati eksplicitno označene namespace-e od onih koji nasleđuju cluster-wide default.

Verzionisanje politike i bezbedna nadogradnja

PSS verzija je vezana za Kubernetes minor izdanje, na primer v1.36. Zaključavanje verzije čini ponašanje predvidivim tokom control plane nadogradnje. latest prati najnoviju politiku koju API server poznaje. Dobra produkciona praksa je da enforce ostane zaključan na testiranu verziju, dok audit/warn privremeno koriste ciljnu noviju verziju. Posle sanacije svih nalaza i testa u preprodukciji pomeraju se enforce labela i dokumentovani kriterijum prihvatanja.

Verzija politike nije isto što i verzija manifesta ili kubelet-a. U klasteru sa skew-om čvorova potrebno je potvrditi da runtime i OS podržavaju tražena polja. Posebno treba proveriti mešovite Linux/Windows klastere: od PSS v1.25 kontrole specifične za Linux, uključujući privilege escalation, seccomp i capabilities, ne primenjuju se kada pod eksplicitno navede spec.os.name: windows. Ako to polje izostane, kubelet pre v1.24 ga ne popunjava i politika može manifest tretirati kao Linux. Node selector i spec.os.name moraju biti međusobno usklađeni.

Cluster-wide konfiguracija i izuzeci

Samoupravni vanilla klaster može proslediti AdmissionConfiguration API serveru parametrom --admission-control-config-file i definisati cluster-wide podrazumevane režime. Konfiguracioni API pod-security.admission.config.k8s.io/v1 zahteva Kubernetes 1.25 ili noviji; 1.23–1.24 koristili su v1beta1, a 1.22 v1alpha1. U upravljanom klasteru pristup parametrima API servera može biti nedostupan i koristi se podržani mehanizam provajdera.

apiVersion: apiserver.config.k8s.io/v1
kind: AdmissionConfiguration
plugins:
  - name: PodSecurity
    configuration:
      apiVersion: pod-security.admission.config.k8s.io/v1
      kind: PodSecurityConfiguration
      defaults:
        enforce: baseline
        enforce-version: v1.36
        audit: restricted
        audit-version: v1.36
        warn: restricted
        warn-version: v1.36
      exemptions:
        usernames: []
        runtimeClasses: []
        namespaces:
          - kube-system

Primer je arhitektonski obrazac, ne univerzalna preporuka da se kube-system trajno izuzme. Izuzeci mogu biti korisni za kontrolisane infrastrukturne komponente, ali su široki: zahtev koji ispuni uslov izuzetka preskače sva tri režima, pa nema ni enforce, ni warn, ni audit evaluacije PSA. Izuzeci se mogu definisati po autentifikovanom korisničkom imenu, RuntimeClass nazivu ili namespace-u i moraju biti eksplicitni.

Izuzimanje krajnjeg korisnika često ne daje očekivani rezultat, jer Deployment-ov Pod kreira controller ServiceAccount, ne korisnik koji je kreirao Deployment. Izuzimanje controller ServiceAccount-a je još opasnije: posredno izuzima Pod template svakog korisnika koji sme da kreira odgovarajući workload. Prednost imaju namenski infrastrukturni namespace, ograničen RBAC, posebni čvorovi, audit i najmanji mogući izuzetak sa vlasnikom i rokom ukidanja.

Projektovanje politike po zonama poverenja

Jedna politika za ceo klaster često ili blokira platformu ili ostavlja aplikacije nedovoljno zaštićene. Produkcioni model razdvaja namespace-e prema funkciji i poverenju. Poslovne aplikacije treba projektovati za Restricted. Nasleđene aplikacije mogu privremeno imati Baseline enforce i Restricted warn/audit uz plan migracije. Infrastrukturni DaemonSet-i kojima su dokazano potrebni hostNetwork, hostPath ili dodatne capabilities smeštaju se u namenski, strogo administriran namespace i na odvojene čvorove kada je moguće.

Zona Predložena PSA osnova Dodatne kontrole
Poslovne aplikacije Restricted enforce, audit i warn na zaključanoj verziji Non-root slike, read-only root FS, NetworkPolicy, digest/potpis slike, minimalni ServiceAccount
Prelazna zona Baseline enforce; Restricted audit/warn Inventar prekršaja, vlasnik, rok sanacije i zabrana novih izuzetaka bez odobrenja
Platformski add-on-i Posebno procenjen profil ili najmanji izuzetak Ograničen RBAC, namenski namespace/čvorovi, provenance slike, runtime monitoring i detaljan audit
Eksperimentalni lab Najmanje Baseline enforce Bez produkcionih tajni i mrežnog puta ka kritičnim sistemima; vremenski ograničen namespace

Validacija pre promene stanja

Server-side dry-run prolazi kroz autentikaciju, autorizaciju, defaulting i admission, ali ne upisuje objekat. Zato je vredniji od samog lokalnog parsiranja YAML-a. Ipak, dry-run ne pokreće scheduler, image pull, CSI montažu ni proces; uspeh znači samo da je zahtev prihvatljiv API serveru u tom trenutku.

kubectl apply --dry-run=server -f workload.yaml

kubectl label --dry-run=server --overwrite namespace secure-apps \
  pod-security.kubernetes.io/enforce=restricted \
  pod-security.kubernetes.io/enforce-version=v1.36

Kada se enforce labela dodaje ili menja, PSA proverava postojeće podove i vraća zbirna upozorenja, ali ne briše već pokrenute podove. Nova politika utiče na buduće kreacije; sledeći rollout, rescheduling ili zamena poda može biti odbijena. Zato namespace ne treba proglasiti usaglašenim samo zato što labela postoji. Potrebno je testirati sve Pod template-e, CronJob/Job scenarije, init kontejnere i operativne ephemeral kontejnere.

Integrisani praktikum: od detekcije do Restricted enforce politike

Poslovni i tehnički kontekst. Tim uvodi namespace secure-apps za internu web aplikaciju. Bezbednosni zahtev je da se poznato privilegovani workload odmah odbije, dok usaglašena aplikacija radi kao ne-root proces bez dodatnih capabilities, bez automatski montiranog API tokena i sa read-only korenskim filesystem-om. Vežba prvo uvodi Baseline enforce i Restricted warn/audit, zatim otklanja nalaze i podiže enforce na Restricted.

Topologija i preduslovi. Potreban je izolovan laboratorijski vanilla Kubernetes klaster 1.25 ili noviji sa omogućenim PodSecurity admission kontrolerom, Linux worker čvorom, funkcionalnim DNS-om i mogućnošću preuzimanja slike nginxinc/nginx-unprivileged:1.27-alpine. Za potpuno ponovljivu produkcionu primenu slika se preslikava u interni registry, skenira i referencira digest-om. Primer zaključava PSS na v1.36; na starijem API serveru mora se koristiti verzija koju taj server poznaje.

Prava i rizik. Izvršilac mora moći da kreira i briše namespace i laboratorijske Deployment/Pod objekte i da čita događaje. Promena namespace labela menja bezbednosnu politiku i u produkciji zahteva odobrenje. Cleanup koji briše namespace je destruktivan i izvršava se samo u izolovanoj laboratoriji.

  1. Proveriti kontekst, verziju i admission ponašanje. Komande su read-only osim server-side dry-run zahteva, koji ne upisuje stanje.
    kubectl config current-context
    kubectl version
    kubectl auth whoami
    kubectl get --raw='/version'
    kubectl get namespace secure-apps
    Ako namespace već postoji, vežba se zaustavlja dok se ne potvrdi vlasnik. Na samoupravnom kubeadm control plane-u ovlašćeni administrator može read-only proveriti static Pod manifest, ali ne treba menjati ga u ovoj vežbi:
    sudo grep -n -- '--enable-admission-plugins\|--disable-admission-plugins\|--admission-control-config-file' \
      /etc/kubernetes/manifests/kube-apiserver.yaml
    Odsustvo eksplicitnog PodSecurity argumenta ne dokazuje da je plugin isključen, jer je podrazumevano omogućen u podržanim izdanjima. Funkcionalni test u narednim koracima daje pouzdaniji dokaz.
  2. Kreirati namespace u postepenom režimu. Ova komanda menja stanje. Baseline se odmah sprovodi, dok Restricted za sada samo upozorava i ostavlja audit anotaciju.
    kubectl create namespace secure-apps
    
    kubectl label namespace secure-apps --overwrite \
      pod-security.kubernetes.io/enforce=baseline \
      pod-security.kubernetes.io/enforce-version=v1.36 \
      pod-security.kubernetes.io/audit=restricted \
      pod-security.kubernetes.io/audit-version=v1.36 \
      pod-security.kubernetes.io/warn=restricted \
      pod-security.kubernetes.io/warn-version=v1.36
    
    kubectl get namespace secure-apps --show-labels
    Očekivani rezultat: namespace sadrži svih šest labela. Ako server prijavi da v1.36 nije validna policy verzija, ne koristi se latest napamet; utvrđuje se verzija control plane-a i manifest prilagođava podržanoj, dokumentovanoj verziji.
  3. Dokazati da Baseline odbija privilegovani Pod. Manifest je namerno nebezbedan i koristi se samo kao server-side dry-run; nijedan Pod se ne upisuje.
    mkdir -p pod-security-lab
    cd pod-security-lab
    
    kubectl -n secure-apps run privileged-probe \
      --image=busybox:1.36.1 \
      --restart=Never \
      --overrides='{"spec":{"containers":[{"name":"privileged-probe","image":"busybox:1.36.1","command":["sleep","3600"],"securityContext":{"privileged":true}}]}}' \
      --dry-run=server -o yaml
    Očekivani rezultat: API server odbija zahtev porukom koja navodi kršenje Baseline kontrole za privileged container. Ako se YAML ipak vrati bez odbijanja, proveravaju se namespace, labele i da li je PodSecurity admission aktivan. Ne uklanja se labela da bi test „prošao”.
  4. Kreirati aplikacioni manifest koji je Baseline-kompatibilan, ali namerno nepotpun za Restricted.
    cat > 01-baseline-app.yaml <<'EOF'
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: web
      namespace: secure-apps
      labels:
        app: web
    spec:
      replicas: 2
      selector:
        matchLabels:
          app: web
      template:
        metadata:
          labels:
            app: web
        spec:
          containers:
            - name: web
              image: nginxinc/nginx-unprivileged:1.27-alpine
              ports:
                - name: http
                  containerPort: 8080
              resources:
                requests:
                  cpu: 25m
                  memory: 32Mi
                limits:
                  cpu: 250m
                  memory: 128Mi
    EOF
    
    kubectl apply --dry-run=server -f 01-baseline-app.yaml
    kubectl apply -f 01-baseline-app.yaml
    Očekivani rezultat: zahtev je dozvoljen pod Baseline enforce režimom, ali kubectl prikazuje Restricted upozorenja, tipično za nedostajuće allowPrivilegeEscalation: false, runAsNonRoot: true, capabilities.drop: ["ALL"] i seccomp profil. Tačan tekst zavisi od policy verzije. Upozorenje se čuva kao nalaz, ne tumači kao neuspeh API-ja.
  5. Prikupiti početne dokaze.
    kubectl -n secure-apps rollout status deployment/web --timeout=120s
    kubectl -n secure-apps get deployment,replicaset,pod -o wide
    kubectl -n secure-apps get events --sort-by='.metadata.creationTimestamp'
    kubectl -n secure-apps get deployment web -o yaml
    Podovi treba da budu Ready. Audit zapis, ako je audit omogućen, treba da sadrži PSA audit anotacije za Restricted prekršaje. Lokacija zavisi od --audit-log-path ili centralnog sistema upravljanog klastera. Warn izlaz terminala nije zamena za audit zapis.
  6. Izraditi Restricted-kompatibilan i dodatno ojačan manifest. Ova verzija dodaje sva ključna Restricted polja, isključuje nepotrebni ServiceAccount token i postavlja read-only root filesystem sa namenskim writable putanjama koje nginx slika koristi.
    cat > 02-restricted-app.yaml <<'EOF'
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: web
      namespace: secure-apps
      labels:
        app: web
    spec:
      replicas: 2
      strategy:
        type: RollingUpdate
        rollingUpdate:
          maxUnavailable: 0
          maxSurge: 1
      selector:
        matchLabels:
          app: web
      template:
        metadata:
          labels:
            app: web
        spec:
          automountServiceAccountToken: false
          securityContext:
            runAsNonRoot: true
            runAsUser: 101
            runAsGroup: 101
            seccompProfile:
              type: RuntimeDefault
          containers:
            - name: web
              image: nginxinc/nginx-unprivileged:1.27-alpine
              ports:
                - name: http
                  containerPort: 8080
              securityContext:
                allowPrivilegeEscalation: false
                readOnlyRootFilesystem: true
                capabilities:
                  drop:
                    - ALL
              readinessProbe:
                httpGet:
                  path: /
                  port: http
                initialDelaySeconds: 2
                periodSeconds: 5
              volumeMounts:
                - name: cache
                  mountPath: /var/cache/nginx
                - name: pid
                  mountPath: /var/run
                - name: tmp
                  mountPath: /tmp
              resources:
                requests:
                  cpu: 25m
                  memory: 32Mi
                limits:
                  cpu: 250m
                  memory: 128Mi
          volumes:
            - name: cache
              emptyDir:
                sizeLimit: 64Mi
            - name: pid
              emptyDir:
                sizeLimit: 8Mi
            - name: tmp
              emptyDir:
                sizeLimit: 32Mi
    EOF
    
    kubectl apply --dry-run=server -f 02-restricted-app.yaml
    Kriterijum: dry-run prolazi bez Restricted upozorenja. Ako slika u korišćenom izdanju zahteva druge writable putanje ili UID, to se potvrđuje dokumentacijom i testom slike; ne vraća se root niti uklanja zaštita bez analize.
  7. Primeniti hardening kroz kontrolisani rollout.
    kubectl apply -f 02-restricted-app.yaml
    kubectl -n secure-apps rollout status deployment/web --timeout=180s
    kubectl -n secure-apps get pods -l app=web
    kubectl -n secure-apps logs deployment/web --tail=50
    Ako rollout ne uspe, prvo se čuvaju događaji i logovi. Bezbedan rollback je kubectl -n secure-apps rollout undo deployment/web, jer je prethodna verzija i dalje Baseline-kompatibilna; zatim se u preprodukciji ispravlja image/permissions problem. Rollback je privremeno zaobilazno rešenje, ne trajno odustajanje od Restricted cilja.
  8. Dokazati efektivni identitet i filesystem kontrole.
    POD_NAME=$(kubectl -n secure-apps get pod -l app=web \
      -o jsonpath='{.items[0].metadata.name}')
    
    kubectl -n secure-apps exec "$POD_NAME" -- id
    kubectl -n secure-apps exec "$POD_NAME" -- sh -c \
      'test "$(id -u)" != "0" && touch /tmp/allowed && ! touch /should-fail'
    
    kubectl -n secure-apps get pod "$POD_NAME" \
      -o jsonpath='{.spec.securityContext.seccompProfile.type}{"\n"}{.spec.containers[0].securityContext}{"\n"}'
    Očekivani rezultat: UID nije 0, upis u /tmp uspeva, upis u root filesystem ne uspeva, a manifest pokazuje RuntimeDefault, uklonjene capabilities i onemogućenu eskalaciju. Komanda ne dokazuje sadržaj kernel capability seta; za dublju proveru koristi se odobren alat ili čitanje /proc/1/status uz pravilno tumačenje heksadecimalnih maski.
  9. Podignuti enforce na Restricted tek nakon dry-run provere.
    kubectl label --dry-run=server --overwrite namespace secure-apps \
      pod-security.kubernetes.io/enforce=restricted \
      pod-security.kubernetes.io/enforce-version=v1.36
    
    kubectl label --overwrite namespace secure-apps \
      pod-security.kubernetes.io/enforce=restricted \
      pod-security.kubernetes.io/enforce-version=v1.36
    
    kubectl get namespace secure-apps --show-labels
    Validacija: labela pokazuje Restricted v1.36, postojeće dve replike ostaju Ready, a ponovno izvršavanje privileged dry-run testa biva odbijeno. Promena ne deložira neusaglašene postojeće podove; zato se radi i kontrolisani restart kako bi se dokazalo da nove kreacije prolaze.
    kubectl -n secure-apps rollout restart deployment/web
    kubectl -n secure-apps rollout status deployment/web --timeout=180s
    kubectl -n secure-apps get events --sort-by='.metadata.creationTimestamp'
  10. Simulirati regresiju i razlikovati mesto kvara. Sledeći patch postoji samo kao server-side dry-run i pokušava da vrati eskalaciju privilegija.
    kubectl -n secure-apps patch deployment web \
      --type='strategic' \
      --patch='{"spec":{"template":{"spec":{"containers":[{"name":"web","securityContext":{"allowPrivilegeEscalation":true}}]}}}}' \
      --dry-run=server
    Warn provera workload template-a treba da prijavi prekršaj. Pošto enforce ne važi direktno za Deployment objekat, ponašanje klijenta i API verzije treba tumačiti zajedno sa konačnim testom stvarnog Pod-a. U produkciji se ne primenjuje ovaj patch samo da bi se proizveo incident.
  11. Dokumentovati prihvatanje i bezbedno očistiti laboratoriju. Prihvatanje zahteva: zaključane namespace labele, uspešan server-side dry-run, dve Ready replike nakon restart-a, ne-root UID, dozvoljen upis samo u deklarisani volume, odbijeni privileged zahtev i sačuvan audit/događajni trag.
    kubectl -n secure-apps get deployment web -o yaml > accepted-deployment.yaml
    kubectl get namespace secure-apps -o yaml > accepted-namespace.yaml
    
    kubectl delete namespace secure-apps
    Poslednja komanda je destruktivna i briše sve namespace resurse. Izvršava se samo ako je secure-apps napravljen isključivo za ovu vežbu. U deljenom okruženju briše se samo Deployment/web, a namespace politika se ne menja bez zasebno odobrene promene.

Troubleshooting: Pod je odbijen ili workload nema replike

Dijagnostika počinje razdvajanjem simptoma. Ako kubectl apply odmah vraća Forbidden sa PSS detaljima, odbijen je direktni Pod zahtev ili druga admission provera. Ako je Deployment kreiran, ali READY 0/2, njegov controller možda ne može da kreira Pod. Ako Pod postoji u stanju CreateContainerConfigError ili CrashLoopBackOff, admission je već prošao i problem je u kubelet/runtime fazi, dozvolama fajlova ili aplikaciji.

kubectl -n secure-apps get deployment,replicaset,pod
kubectl -n secure-apps describe deployment web
kubectl -n secure-apps describe replicaset -l app=web
kubectl -n secure-apps get events --sort-by='.metadata.creationTimestamp'
kubectl -n secure-apps logs deployment/web --all-containers --previous

Read-only redosled je: potvrditi kontekst i namespace labele; sačuvati primljenu admission poruku; pronaći vlasnički lanac Deployment–ReplicaSet–Pod; pregledati događaje; uporediti efektivni Pod template sa PSS verzijom; zatim proveriti runtime i aplikacione logove. Tek posle postavljene hipoteze menja se manifest. Uklanjanje enforce labele ili prebacivanje namespace-a na Privileged nije dijagnostika, već proširenje attack surface-a.

Simptom Neposredni uzrok Bezbedna korektivna mera
violates PodSecurity "restricted" Jedno ili više polja regularnog, init ili ephemeral kontejnera krši zaključani profil. Popisati svako navedeno polje, ispraviti sliku/manifest i ponoviti server-side dry-run.
Deployment postoji, ReplicaSet stalno prijavljuje FailedCreate Controller-ov Pod zahtev odbija enforce, quota ili drugi admission plugin. Pregledati ReplicaSet događaje i tačnu poruku; ne tražiti aplikacioni log poda koji nikada nije kreiran.
runAsNonRoot greška za sliku Slika podrazumevano koristi root ili sadrži nenumerički USER koji runtime ne može potvrditi. Izgraditi sliku sa numeričkim ne-root USER-om i pravilnim vlasništvom; runAsUser dodati tek posle testa dozvola.
Permission denied nakon non-root promene Konfiguracija, volume ili aplikaciona putanja pripada root-u. Ispraviti image build ili volume ownership; pažljivo koristiti fsGroup. Privilegovani init chown nije prihvatljiva podrazumevana zakrpa.
Aplikacija pada uz read-only filesystem Pokušava upis u neidentifikovanu putanju. Iz loga utvrditi tačnu putanju i montirati ograničen emptyDir ili odgovarajući PVC; ne vraćati ceo root FS na writable bez analize.
Seccomp operation not permitted RuntimeDefault blokira syscall ili aplikacija pogrešno tumači drugi permission problem. Korelisati runtime/kern log, syscall i verziju slike; prilagoditi aplikaciju ili kontrolisani Localhost profil. Unconfined je samo kratka izolovana dijagnostička hipoteza, ne produkciono rešenje.
Promena namespace labele nije ugasila stare Podove PSA ne vrši retroaktivnu deložaciju. Sanirati template i sprovesti kontrolisani rollout uz PDB i proveru kapaciteta.

Incident response: neočekivani privilegovani workload

  1. Odrediti simptom, obim i posledicu. Identifikovati namespace, Pod UID, owner references, čvor, image digest, ServiceAccount, host namespace/volume upotrebu, capabilities i vreme kreiranja. Privileged Pod na čvoru može značiti potencijalni incident na nivou čvora, ne samo aplikacije.
  2. Sačuvati dokaze pre promene. Sačuvati Pod/Deployment/DaemonSet manifest, događaje, audit zapise za kreiranje i promenu namespace labela, runtime/kubelet logove, listu zahvaćenih čvorova i relevantne mrežne tokove. Secret i token vrednosti se ne kopiraju u tiket.
  3. Postaviti hipotezu. Razlikovati odobren infrastrukturni izuzetak, pogrešnu ili uklonjenu PSA labelu, globalni izuzetak, kompromitovan administratorski identitet, webhook fail-open i stariji Pod koji je postojao pre pooštravanja politike.
  4. Ograničiti štetu. Uz incidentnog vlasnika sprečiti nove kreacije vraćanjem odobrene namespace politike i uklanjanjem neovlašćenog RBAC pristupa. Izolovati čvor i workload mrežno kada rizik opravdava prekid. Brisanje poda pre prikupljanja dokaza može ukloniti važan trag i izazvati automatsko ponovno kreiranje.
  5. Iskoreniti uzrok. Ukloniti neodobreni controller, rotirati kompromitovane akreditacije, vratiti Git-known-good manifeste, pregledati host zbog promena i po potrebi reprovisionovati čvor iz pouzdanog izvora. Samo dodavanje PSA labele ne poništava radnje koje je privilegovani proces već izvršio.
  6. Oporaviti i dokazati. Ponovo rasporediti usaglašen workload, potvrditi Restricted/odobrenu politiku, negativni privileged dry-run, Ready stanje, audit kontinuitet i odsustvo neočekivanih procesa ili fajlova na čvoru.
  7. Sprečiti ponavljanje. Ograničiti pravo menjanja namespace labela i admission konfiguracije, uvesti alarm za privilegovane karakteristike i PSA exemptions metriku, kontrolisati image provenance i definisati rok svakom izuzetku.

Metrike, događaji, audit i logovi

API server izlaže Prometheus metrike pod_security_evaluations_total, pod_security_exemptions_total i pod_security_errors_total. Prva pokazuje broj evaluacija po odluci, režimu, nivou i verziji; druga korišćenje izuzetaka; treća interne greške evaluacije. Rast exemptions zahteva korelaciju sa odobrenim infrastrukturnim operacijama. Greška evaluacije nije isto što i legitimno odbijanje manifesta i zahteva hitnu proveru API servera.

User-facing warn poruka živi u odgovoru klijentu. Controller-ov FailedCreate obično se vidi u događajima ReplicaSet-a, DaemonSet-a ili Job-a. Audit režim dodaje anotacije audit događaju; njihov opstanak zavisi od audit nivoa i backend-a. Na kubeadm čvoru API server radi kao static Pod, pa se logovi čitaju preko container runtime-a ili centralne platforme. Journal putanja je relevantna samo kada je komponenta zaista systemd servis. Uvek se bira vremenski opseg oko requestReceivedTimestamp i korelišu korisnik, source IP, user agent, namespace, resurs, odgovor i audit ID.

kubectl -n secure-apps get events \
  --field-selector=reason=FailedCreate \
  --sort-by='.metadata.creationTimestamp'

kubectl get --raw='/metrics' \
  | grep '^pod_security_'

Druga komanda je read-only, ali pristup /metrics je obično ograničen i izlaz može biti velik. U produkciji se preferira odobreni Prometheus scrape i dashboard sa trendovima. Ako korisnik nema pravo, ne dodeljuje mu se široka API privilegija samo radi jednokratnog čitanja metrike.

Administratorske prakse

Završna kontrolna tačka

Zaključak

SecurityContext pretvara očekivanja o privilegijama procesa u deo deklarativnog ugovora workload-a. Non-root UID, zabrana eskalacije, minimalne capabilities, seccomp, read-only root filesystem i pažljivo projektovane volume dozvole smanjuju mogućnost da kompromitovan proces preraste u kompromitovanje poda ili čvora. Međutim, ta polja vrede samo ako ih slika, runtime i OS stvarno podržavaju i ako se rezultat proveri u izvršavanju.

Pod Security Standards daju zajednički, verzionisani minimum, a Pod Security Admission taj minimum sprovodi u API putanji pre kreiranja poda. Produkciona zrelost vidi se u postepenom prelasku sa audit/warn na enforce, zaključavanju testirane verzije, odvojenim zonama poverenja i izuzetku koji je uži od problema koji rešava. Privileged namespace bez vlasnika i roka nije operativna fleksibilnost, već trajno otvorena granica napada.

Administrator posle ove lekcije ne procenjuje bezbednost po tome da li manifest sadrži nekoliko poznatih polja. On dokazuje koju politiku API server primenjuje, da li controller uspeva da kreira replike, pod kojim UID/GID proces radi, koje putanje su writable, koje operacije su odbijene, gde je audit trag i šta se dešava pri nadogradnji ili rollback-u. Time admission prestaje da bude prepreka deployment-u i postaje kontrolisana zaštita platforme.

← Prethodna lekcija Povratak na sadržaj Sledeća lekcija →