Це багатосторінковий друкований вигляд цього розділу. Натисність щоб друкувати.

Повернутися до звичайного перегляду сторінки.

Безпека

Безпека є важливою темою для більшості організацій та людей, які використовують кластери Kubernetes. Ви можете знайти базовий список перевірок безпеки в іншому місці документації Kubernetes.

Щоб дізнатися, як розгорнути та керувати аспектами безпеки Kubernetes, ви можете виконати практичні завдання в цьому розділі.

1 - Застосування стандартів безпеки Podʼів на рівні кластера

Безпека Pod покладається на контролер допуску, що виконує перевірки відповідно до Стандартів безпеки Podʼів в Kubernetes при створенні нових Podʼів. Це функція, має загальну доступність з випуску v1.25. Цей навчальний посібник показує, як застосувати стандарт безпеки baseline на рівні кластера, що застосовує стандартну конфігурацію для всіх просторів імен у кластері.

Для застосування стандартів безпеки Podʼів до певних просторів імен дивіться Застосування стандартів безпеки Podʼів на рівні простору імен.

Якщо ви працюєте з версією Kubernetes, відмінною від v1.31, перевірте документацію для вашої версії.

Перш ніж ви розпочнете

Встановіть на ваш компʼютер наступне:

Цей навчальний посібник показує, як ви можете налаштувати кластер Kubernetes, який ви повністю контролюєте. Якщо ви вивчаєте, як налаштувати Pod Security Admission для кластера, що надається постачальником послуг, де ви не можете налаштувати панель управління, прочитайте Застосування стандартів безпеки Podʼів на рівні простору імен.

Виберіть правильний стандарт безпеки Podʼів

Pod Security Admission дозволяє застосовувати вбудовані Стандарти безпеки Podʼів у режимах: enforce, audit та warn.

Щоб зібрати інформацію, яка допоможе вам вибрати стандарти безпеки Podʼів, які найбільш підходять для вашої конфігурації, виконайте наступне:

  1. Створіть кластер, в якому не застосовані стандарти безпеки Podʼів:

    kind create cluster --name psa-wo-cluster-pss
    

    Вивід подібний до:

    Creating cluster "psa-wo-cluster-pss" ...
    ✓ Ensuring node image (kindest/node:v1.31.0) 🖼
    ✓ Preparing nodes 📦
    ✓ Writing configuration 📜
    ✓ Starting control-plane 🕹️
    ✓ Installing CNI 🔌
    ✓ Installing StorageClass 💾
    Set kubectl context to "kind-psa-wo-cluster-pss"
    You can now use your cluster with:
    
    kubectl cluster-info --context kind-psa-wo-cluster-pss
    
    Thanks for using kind! 😊
    
  2. Встановіть контекст kubectl для нового кластера:

    kubectl cluster-info --context kind-psa-wo-cluster-pss
    

    Вивід подібний до цього:

    Kubernetes control plane is running at https://127.0.0.1:61350
    
    CoreDNS is running at https://127.0.0.1:61350/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
    
    To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.
    
  3. Отримайте список просторів імен у кластері:

    kubectl get ns
    

    Вивід подібний до цього:

    NAME                 STATUS   AGE
    default              Active   9m30s
    kube-node-lease      Active   9m32s
    kube-public          Active   9m32s
    kube-system          Active   9m32s
    local-path-storage   Active   9m26s
    
  4. Використовуйте --dry-run=server, щоб зрозуміти, що відбувається при застосуванні різних стандартів безпеки Podʼів:

    1. Privileged

      kubectl label --dry-run=server --overwrite ns --all \
      pod-security.kubernetes.io/enforce=privileged
      

      Вивід подібний до:

      namespace/default labeled
      namespace/kube-node-lease labeled
      namespace/kube-public labeled
      namespace/kube-system labeled
      namespace/local-path-storage labeled
      
    2. Baseline

      kubectl label --dry-run=server --overwrite ns --all \
      pod-security.kubernetes.io/enforce=baseline
      

      Вивід подібний до:

      namespace/default labeled
      namespace/kube-node-lease labeled
      namespace/kube-public labeled
      Warning: existing pods in namespace "kube-system" violate the new PodSecurity enforce level "baseline:latest"
      Warning: etcd-psa-wo-cluster-pss-control-plane (and 3 other pods): host namespaces, hostPath volumes
      Warning: kindnet-vzj42: non-default capabilities, host namespaces, hostPath volumes
      Warning: kube-proxy-m6hwf: host namespaces, hostPath volumes, privileged
      namespace/kube-system labeled
      namespace/local-path-storage labeled
      
    3. Restricted

      kubectl label --dry-run=server --overwrite ns --all \
      pod-security.kubernetes.io/enforce=restricted
      

      Вивід подібний до:

      namespace/default labeled
      namespace/kube-node-lease labeled
      namespace/kube-public labeled
      Warning: existing pods in namespace "kube-system" violate the new PodSecurity enforce level "restricted:latest"
      Warning: coredns-7bb9c7b568-hsptc (and 1 other pod): unrestricted capabilities, runAsNonRoot != true, seccompProfile
      Warning: etcd-psa-wo-cluster-pss-control-plane (and 3 other pods): host namespaces, hostPath volumes, allowPrivilegeEscalation != false, unrestricted capabilities, restricted volume types, runAsNonRoot != true
      Warning: kindnet-vzj42: non-default capabilities, host namespaces, hostPath volumes, allowPrivilegeEscalation != false, unrestricted capabilities, restricted volume types, runAsNonRoot != true, seccompProfile
      Warning: kube-proxy-m6hwf: host namespaces, hostPath volumes, privileged, allowPrivilegeEscalation != false, unrestricted capabilities, restricted volume types, runAsNonRoot != true, seccompProfile
      namespace/kube-system labeled
      Warning: existing pods in namespace "local-path-storage" violate the new PodSecurity enforce level "restricted:latest"
      Warning: local-path-provisioner-d6d9f7ffc-lw9lh: allowPrivilegeEscalation != false, unrestricted capabilities, runAsNonRoot != true, seccompProfile
      namespace/local-path-storage labeled
      

З попередніх результатів ви можете помітити, що застосування стандарту безпеки privileged не показує жодних попереджень для жодного простору імен. Однак для стандартів baseline і restricted є попередження, зокрема для простору імен kube-system.

Встановлення режимів, версій та стандартів

У цьому розділі ви застосовуєте наступні Стандарти безпеки Pod до версії latest:

  • Стандарт baseline у режимі enforce.
  • Стандарт restricted у режимах warn та audit.

Стандарт безпеки Pod baseline надає зручний проміжний рівень, який дозволяє тримати список винятків коротким та запобігає відомим підвищенням привілеїв.

Додатково, для запобігання витоку підсистеми kube-system, ви виключите простір імен з застосуванням Стандартів безпеки Pod.

При впровадженні перевірки безпеки Pod у власному середовищі оберіть такі пункти:

  1. Враховуючи рівень ризику, застосований до кластера, строгий Стандарт безпеки Pod, наприклад restricted, може бути кращим вибором.

  2. Виключення простору імен kube-system дозволяє підсистемам працювати з підвищеними привілеями у цьому просторі імен. Для реального використання проєкт Kubernetes наполегливо рекомендує вам застосовувати строгі політики RBAC, які обмежують доступ до kube-system, слідуючи принципу найменших привілеїв. Для впровадження вищезазначених стандартів виконайте наступне:

  3. Створіть файл конфігурації, який може бути використаний Контролером допуску Стандартів безпеки Pod для застосування цих Стандартів безпеки Pod:

    mkdir -p /tmp/pss
    cat <<EOF > /tmp/pss/cluster-level-pss.yaml
    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: "latest"
          audit: "restricted"
          audit-version: "latest"
          warn: "restricted"
          warn-version: "latest"
        exemptions:
          usernames: []
          runtimeClasses: []
          namespaces: [kube-system]
    EOF
    
  4. Налаштуйте API-сервер для використання цього файлу під час створення кластера:

    cat <<EOF > /tmp/pss/cluster-config.yaml
    kind: Cluster
    apiVersion: kind.x-k8s.io/v1alpha4
    nodes:
    - role: control-plane
      kubeadmConfigPatches:
      - |
        kind: ClusterConfiguration
        apiServer:
            extraArgs:
              admission-control-config-file: /etc/config/cluster-level-pss.yaml
            extraVolumes:
              - name: accf
                hostPath: /etc/config
                mountPath: /etc/config
                readOnly: false
                pathType: "DirectoryOrCreate"
      extraMounts:
      - hostPath: /tmp/pss
        containerPath: /etc/config
        readOnly: false
        selinuxRelabel: false
        propagation: None
    EOF
    
  5. Створіть кластер, який використовує Pod Security Admission для застосування цих Стандартів безпеки Pod:

    kind create cluster --name psa-with-cluster-pss --config /tmp/pss/cluster-config.yaml
    

    Вивід буде подібним до цього:

    Creating cluster "psa-with-cluster-pss" ...
     ✓ Ensuring node image (kindest/node:v1.31.0) 🖼
     ✓ Preparing nodes 📦
     ✓ Writing configuration 📜
     ✓ Starting control-plane 🕹️
     ✓ Installing CNI 🔌
     ✓ Installing StorageClass 💾
    Set kubectl context to "kind-psa-with-cluster-pss"
    You can now use your cluster with:
    
    kubectl cluster-info --context kind-psa-with-cluster-pss
    
    Have a question, bug, or feature request? Let us know! https://kind.sigs.k8s.io/#community 🙂
    
  6. Вкажіт kubectl кластер:

    kubectl cluster-info --context kind-psa-with-cluster-pss
    

    Вивід буде схожий на цей:

    Kubernetes control plane is running at https://127.0.0.1:63855
    CoreDNS is running at https://127.0.0.1:63855/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
    
    To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.
    
  7. Створіть Pod у просторі імен default:

    apiVersion: v1
     kind: Pod
     metadata:
       name: nginx
     spec:
       containers:
         - image: nginx
           name: nginx
           ports:
             - containerPort: 80
     
    kubectl apply -f https://k8s.io/examples/security/example-baseline-pod.yaml
    

    Pod запускається звичайно, але вивід містить попередження:

    Warning: would violate 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")
    pod/nginx created
    

Очищення

Тепер ви можете видалити кластери, які ви створили:

kind delete cluster --name psa-with-cluster-pss
kind delete cluster --name psa-wo-cluster-pss

Що далі

2 - Застосування Стандартів безпеки Pod на рівні простору імен

Pod Security Admission — це контролер допуску, який застосовує Стандарти безпеки Pod при створенні Podʼів. Це функція, яка є загально доступною з v1.25. У цьому посібнику ви будете застосовувати Стандарт безпеки Pod baseline, по одному простору імен за раз.

Ви також можете застосовувати Стандарти безпеки Pod до кількох просторів імен одночасно на рівні кластера. Щоб дізнатися більше, перейдіть за посиланням Застосування Стандартів безпеки Pod на рівні кластера.

Перш ніж ви розпочнете

Встановіть на ваш компʼютер наступне:

Створення кластера

  1. Створіть кластер kind наступним чином:

    kind create cluster --name psa-ns-level
    

    Вивід буде подібний до цього:

    Creating cluster "psa-ns-level" ...
     ✓ Ensuring node image (kindest/node:v1.31.0) 🖼 
     ✓ Preparing nodes 📦  
     ✓ Writing configuration 📜 
     ✓ Starting control-plane 🕹️ 
     ✓ Installing CNI 🔌 
     ✓ Installing StorageClass 💾 
    Set kubectl context to "kind-psa-ns-level"
    You can now use your cluster with:
    
    kubectl cluster-info --context kind-psa-ns-level
    
    Not sure what to do next? 😅  Check out https://kind.sigs.k8s.io/docs/user/quick-start/
    
  2. Встановіть контекст kubectl для нового кластера:

    kubectl cluster-info --context kind-psa-ns-level
    

    Вивід буде подібний до цього:

    Kubernetes control plane is running at https://127.0.0.1:50996
    CoreDNS is running at https://127.0.0.1:50996/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
    
    To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.
    

Створення простору імен

Створіть новий простір імен з назвою example:

kubectl create ns example

Вивід буде подібний до цього:

namespace/example created

Увімкнення перевірки Стандартів безпеки Pod для цього простору імен

  1. Увімкніть Стандарти безпеки Pod на цьому просторі імен за допомогою підтримуваних міток, вбудованих в Pod Security Admission. На цьому кроці ви налаштуєте перевірку, щоб система попереджувала про Podʼи, які не відповідають останньої версії стандарту безпеки підсистеми baseline.

    kubectl label --overwrite ns example \
       pod-security.kubernetes.io/warn=baseline \
       pod-security.kubernetes.io/warn-version=latest
    
  2. Ви можете налаштувати кілька перевірок стандартів безпеки Podʼів для будь-якого простору імен за допомогою міток. Наступна команда буде застосовувати стандарт безпеки Pod baseline, але warn та audit для стандартів безпеки Pod restricted згідно з останньою версією (стандартне значення)

    kubectl label --overwrite ns example \
      pod-security.kubernetes.io/enforce=baseline \
      pod-security.kubernetes.io/enforce-version=latest \
      pod-security.kubernetes.io/warn=restricted \
      pod-security.kubernetes.io/warn-version=latest \
      pod-security.kubernetes.io/audit=restricted \
      pod-security.kubernetes.io/audit-version=latest
    

Перевірте дотримання стандарту безпеки Podʼів

  1. Створіть Pod з базовим стандартом в просторі імен example:

    kubectl apply -n example -f https://k8s.io/examples/security/example-baseline-pod.yaml
    

    Pod дійсно запускається; вивід містить попередження. Наприклад:

    Warning: would violate 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")
    pod/nginx created
    
  2. Створіть Pod з базовим стандартом у просторі імен default:

    kubectl apply -n default -f https://k8s.io/examples/security/example-baseline-pod.yaml
    

    Вивід буде подібний до такого:

    pod/nginx створено
    

Виконання стандартів безпеки Podʼів та налаштування попереджень було застосовано лише до простору імен example. Ви можете створити такий самий Pod в просторі імен default без будь-яких попереджень.

Очищення

Тепер видаліть кластер, який було створено:

kind delete cluster --name psa-ns-level

Що далі

3 - Обмежте доступ контейнера до ресурсів за допомогою AppArmor

СТАН ФУНКЦІОНАЛУ: Kubernetes v1.31 [stable]

Ця сторінка показує, як завантажити профілі AppArmor на ваших вузлах та забезпечити їх виконання в Podʼах. Щоб дізнатися більше про те, як Kubernetes може обмежувати Podʼи за допомогою AppArmor, див. Обмеження безпеки ядра Linux для Podʼів та контейнерів.

Цілі

  • Ознайомитись з прикладом того, як завантажити профіль на Вузол
  • Дізнайтеся, як забезпечити виконання профілю в Podʼі
  • Дізнайтеся, як перевірити, що профіль завантажено
  • Побачте, що відбувається, коли умови профілю порушуються
  • Побачте, що відбувається, якщо профіль не може бути завантажено

Перш ніж ви розпочнете

AppArmor — це необовʼязковий модуль ядра та функція Kubernetes, тому переконайтеся, що він підтримується на ваших вузлах перед продовженням:

  1. Модуль ядра AppArmor увімкнено — для того, щоб ядро Linux застосовувало профіль AppArmor, модуль ядра AppArmor повинен бути встановлений та увімкнений. Декілька дистрибутивів стандартно вмикають модуль, такі як Ubuntu і SUSE, і багато інших надають опціональну підтримку. Щоб перевірити, чи модуль увімкнено, перевірте файл /sys/module/apparmor/parameters/enabled:

    cat /sys/module/apparmor/parameters/enabled
    Y
    

    Kubelet перевіряє, чи AppArmor увімкнено на хості, перед тим як допустити Pod з явно налаштованим AppArmor.

  2. Середовище виконання контейнерів підтримує AppArmor — всі загальні середовища виконання контейнерів, що підтримуються Kubernetes, мають підтримку AppArmor, включаючи containerd та CRI-O. Будь ласка, зверніться до відповідної документації середовища та перевірте, що кластер відповідає вимогам до використання AppArmor.

  3. Профіль завантажено — AppArmor застосовується до Podʼа, вказуючи профіль AppArmor, з яким кожен контейнер повинен працювати. Якщо будь-які вказані профілі не завантажені в ядро, Kubelet відхилить Pod. Ви можете переглянути завантажені профілі на вузлі, перевіривши файл /sys/kernel/security/apparmor/profiles. Наприклад:

    ssh gke-test-default-pool-239f5d02-gyn2 "sudo cat /sys/kernel/security/apparmor/profiles | sort"
    
    apparmor-test-deny-write (enforce)
    apparmor-test-audit-write (enforce)
    docker-default (enforce)
    k8s-nginx (enforce)
    

    Для отримання додаткової інформації щодо завантаження профілів на вузли, див. Налаштування вузлів з профілями.

Захист Podʼа

Профілі AppArmor можна вказати на рівні Podʼа або на рівні контейнера. Профіль AppArmor контейнера перевагу перед профілем Podʼа.

securityContext:
  appArmorProfile:
    type: <тип_профілю>

Де <тип_профілю> є одним з:

  • RuntimeDefault для використання типового профілю виконавчого середовища
  • Localhost для використання профілю, завантаженого на хост (див. нижче)
  • Unconfined для запуску без AppArmor

Для отримання повної інформації про API профілю AppArmor див. Вказівки щодо обмеження AppArmor.

Щоб перевірити, що профіль був застосований, ви можете перевірити, що кореневий процес контейнера працює з правильним профілем, переглянувши його атрибут proc:

kubectl exec <імʼя_пода> -- cat /proc/1/attr/current

Вивід повинен виглядати приблизно так:

cri-containerd.apparmor.d (enforce)

Приклад

Цей приклад передбачає, що ви вже налаштували кластер з підтримкою AppArmor.

Спочатку завантажте профіль, який ви хочете використовувати на вузлах. Цей профіль блокує всі операції запису файлів:

#include <tunables/global>

profile k8s-apparmor-example-deny-write flags=(attach_disconnected) {
  #include <abstractions/base>

  file,

   # Deny all file writes.
  deny /** w,
}

Профіль повинен бути завантажений на всі вузли, оскільки ви не знаєте, де буде заплановано Pod. Для цього прикладу ви можете використовувати SSH для встановлення профілів, але існують інші методи, які обговорюються в Налаштуваннях вузлів з профілями.

# Цей приклад передбачає, що імена вузлів відповідають іменам хостів і доступні через SSH.
NODES=($(kubectl get nodes -o name))

for NODE in ${NODES[*]}; do ssh $NODE 'sudo apparmor_parser -q <<EOF
#include <tunables/global>

profile k8s-apparmor-example-deny-write flags=(attach_disconnected) {
  #include <abstractions/base>

  file,

  # Заборонити всі записи файлів.
  deny /** w,
}
EOF'
done

Потім запустіть простий Pod "Hello AppArmor" з профілем deny-write:

apiVersion: v1
kind: Pod
metadata:
  name: hello-apparmor
spec:
  securityContext:
    appArmorProfile:
      type: Localhost
      localhostProfile: k8s-apparmor-example-deny-write
  containers:
  - name: hello
    image: busybox:1.28
    command: [ "sh", "-c", "echo 'Hello AppArmor!' && sleep 1h" ]
kubectl create -f hello-apparmor.yaml

Ви можете перевірити, що контейнер дійсно працює з тим профілем, перевіривши /proc/1/attr/current:

kubectl exec hello-apparmor -- cat /proc/1/attr/current

Вивід повинен бути:

k8s-apparmor-example-deny-write (enforce)

Нарешті, ви можете побачити, що відбудеться, якщо ви порушите профіль, здійснивши спробу зупису у файл:

kubectl exec hello-apparmor -- touch /tmp/test
touch: /tmp/test: Permission denied
error: error executing remote command: command terminated with non-zero exit code: Error executing in Docker Container: 1

Щоб завершити, подивіться, що станеться, якщо ви спробуєте вказати профіль, який не був завантажений:

kubectl create -f /dev/stdin <<EOF
apiVersion: v1
kind: Pod
metadata:
  name: hello-apparmor-2
spec:
  securityContext:
    appArmorProfile:
      type: Localhost
      localhostProfile: k8s-apparmor-example-allow-write
  containers:
  - name: hello
    image: busybox:1.28
    command: [ "sh", "-c", "echo 'Hello AppArmor!' && sleep 1h" ]
EOF
pod/hello-apparmor-2 created

Хоча Pod було створено успішно, подальший огляд покаже, що він застряг в стані очікування:

kubectl describe pod hello-apparmor-2
Name:          hello-apparmor-2
Namespace:     default
Node:          gke-test-default-pool-239f5d02-x1kf/10.128.0.27
Start Time:    Tue, 30 Aug 2016 17:58:56 -0700
Labels:        <none>
Annotations:   container.apparmor.security.beta.kubernetes.io/hello=localhost/k8s-apparmor-example-allow-write
Status:        Pending
... 
Events:
  Type     Reason     Age              From               Message
  ----     ------     ----             ----               -------
  Normal   Scheduled  10s              default-scheduler  Successfully assigned default/hello-apparmor to gke-test-default-pool-239f5d02-x1kf
  Normal   Pulled     8s               kubelet            Successfully pulled image "busybox:1.28" in 370.157088ms (370.172701ms including waiting)
  Normal   Pulling    7s (x2 over 9s)  kubelet            Pulling image "busybox:1.28"
  Warning  Failed     7s (x2 over 8s)  kubelet            Error: failed to get container spec opts: failed to generate apparmor spec opts: apparmor profile not found k8s-apparmor-example-allow-write
  Normal   Pulled     7s               kubelet            Successfully pulled image "busybox:1.28" in 90.980331ms (91.005869ms including waiting)

Event надає повідомлення про помилку з причиною, конкретне формулювання залежить від виконавчого середовища:

  Warning  Failed     7s (x2 over 8s)  kubelet            Error: failed to get container spec opts: failed to generate apparmor spec opts: apparmor profile not found 

Адміністрування

Налаштування вузлів з профілями

Kubernetes 1.31 не надає вбудованих механізмів для завантаження профілів AppArmor на Вузли. Профілі можна завантажити через власну інфраструктуру або інструменти, такі як Оператор профілів безпеки Kubernetes.

Планувальник не знає, які профілі завантажені на який Вузол, тому повний набір профілів повинні бути завантажені на кожен Вузол. Альтернативним підходом є додавання мітки Вузла для кожного профілю (або клас профілів) на Вузлі та використання селектора вузла, щоб забезпечити виконання Podʼа на Вузлі з потрібним профілем.

Створення профілів

Встановлення правильних профілів AppArmor може бути складною справою. На щастя, існують деякі інструменти, що допомагають у цьому:

  • aa-genprof та aa-logprof генерують правила профілю, моніторячи діяльність програми та логи та дозволяючи дії, які вона виконує. Додаткові інструкції надаються у документації AppArmor.
  • bane — генератор профілів AppArmor для Docker, який використовує спрощену мову профілю.

Для дослідження проблем з AppArmor ви можете перевірити системні логи, щоб побачити, що саме було заборонено. AppArmor логує вичерпні повідомлення в dmesg, а помилки зазвичай можна знайти в системних логах або за допомогою journalctl. Більше інформації надається в розділі AppArmor failures.

Вказівки щодо обмеження AppArmor

Профіль AppArmor у контексті безпеки

Ви можете вказати appArmorProfile як у контексті безпеки контейнера, так і в контексті безпеки Podʼа. Якщо профіль встановлено на ні Podʼа, він буде використовуватися як стандартний профіль всіх контейнерів у Podʼі (включаючи контейнери ініціалізації, допоміжний та тимчасовий контейнери). Якщо вказані профілі Podʼа та контейнера, буде використано профіль контейнера.

Профіль AppArmor має 2 поля:

type (обовʼязково) — вказує, який тип профілю AppArmor буде застосований. Дійсні варіанти:

Localhost
профіль, завантажений на вузол (вказаний як localhostProfile).
RuntimeDefault
типовий профіль виконавчого середовища контейнера.
Unconfined
без застосування AppArmor.

localhostProfile — Назва профілю, завантаженого на вузол, який слід використовувати. Профіль повинен бути попередньо налаштований на вузлі, щоб працювати. Цей параметр потрібно вказувати лише в разі, якщо type — Localhost.

Що далі

Додаткові ресурси:

4 - Обмеження системних викликів контейнера за допомогою seccomp

СТАН ФУНКЦІОНАЛУ: Kubernetes v1.19 [stable]

Seccomp походить від secure computing mode (безпечний режим обчислення) і є функцією ядра Linux з версії 2.6.12. Його можна використовувати для ізоляції привілеїв процесу, обмежуючи виклики, які він може здійснювати з простору користувача в ядро. Kubernetes дозволяє автоматично застосовувати профілі seccomp, завантажені на вузол, до ваших Podʼів та контейнерів.

Визначення необхідних привілеїв для ваших робочих навантажень може бути важким завданням. У цьому посібнику ви дізнаєтеся, як завантажувати профілі seccomp в локальний кластер Kubernetes, як застосовувати їх до Podʼа та як ви можете почати створювати профілі, які надають лише необхідні привілеї процесам вашого контейнера.

Цілі

  • Навчитися завантажувати профілі seccomp на вузол
  • Навчитися застосовувати профіль seccomp до контейнера
  • Спостерігати за аудитом системних викликів, здійснюваних процесом контейнера
  • Спостерігати поведінку при відсутності вказаного профілю
  • Спостерігати порушення профілю seccomp
  • Навчитися створювати деталізовані профілі seccomp
  • Навчитися застосовувати стандартний профіль seccomp для контейнера

Перш ніж ви розпочнете

Для завершення всіх кроків у цьому посібнику вам потрібно встановити kind та kubectl.

Команди, які використовуються в посібнику, передбачають, що ви використовуєте Docker як ваше середовище для виконання контейнерів. (Кластер, який створює kind, може використовувати інше контейнерне середовище також). Ви також можете використовувати Podman, але у цьому випадку вам доведеться дотримуватися відповідних інструкцій, щоб успішно виконати завдання.

У цьому посібнику показано деякі приклади, які є бета-версією (починаючи з v1.25) і інші, які використовують лише загальнодоступну функціональність seccomp. Вам слід переконатися, що ваш кластер правильно налаштований для версії, яку ви використовуєте.

У посібнику також використовується інструмент curl для завантаження прикладів на ваш компʼютер. Ви можете адаптувати кроки, щоб використовувати інший інструмент, якщо вам це зручно.

Завантаження прикладів профілів seccomp

Вміст цих профілів буде досліджено пізніше, але наразі продовжте та завантажте їх у каталог з назвою profiles/, щоб їх можна було завантажити в кластер.

{
    "defaultAction": "SCMP_ACT_LOG"
}

{
    "defaultAction": "SCMP_ACT_ERRNO"
}

{
    "defaultAction": "SCMP_ACT_ERRNO",
    "architectures": [
        "SCMP_ARCH_X86_64",
        "SCMP_ARCH_X86",
        "SCMP_ARCH_X32"
    ],
    "syscalls": [
        {
            "names": [
                "accept4",
                "epoll_wait",
                "pselect6",
                "futex",
                "madvise",
                "epoll_ctl",
                "getsockname",
                "setsockopt",
                "vfork",
                "mmap",
                "read",
                "write",
                "close",
                "arch_prctl",
                "sched_getaffinity",
                "munmap",
                "brk",
                "rt_sigaction",
                "rt_sigprocmask",
                "sigaltstack",
                "gettid",
                "clone",
                "bind",
                "socket",
                "openat",
                "readlinkat",
                "exit_group",
                "epoll_create1",
                "listen",
                "rt_sigreturn",
                "sched_yield",
                "clock_gettime",
                "connect",
                "dup2",
                "epoll_pwait",
                "execve",
                "exit",
                "fcntl",
                "getpid",
                "getuid",
                "ioctl",
                "mprotect",
                "nanosleep",
                "open",
                "poll",
                "recvfrom",
                "sendto",
                "set_tid_address",
                "setitimer",
                "writev",
                "fstatfs",
                "getdents64",
                "pipe2",
                "getrlimit"
            ],
            "action": "SCMP_ACT_ALLOW"
        }
    ]
}

Виконайте ці команди:

mkdir ./profiles
curl -L -o profiles/audit.json https://k8s.io/examples/pods/security/seccomp/profiles/audit.json
curl -L -o profiles/violation.json https://k8s.io/examples/pods/security/seccomp/profiles/violation.json
curl -L -o profiles/fine-grained.json https://k8s.io/examples/pods/security/seccomp/profiles/fine-grained.json
ls profiles

Ви повинні побачити три профілі, перераховані в кінці останнього кроку:

audit.json  fine-grained.json  violation.json

Створення локального кластера Kubernetes за допомогою kind

Для спрощення можна використовувати kind, щоб створити одновузловий кластер з завантаженими профілями seccomp. Kind запускає Kubernetes в Docker, тому кожен вузол кластера є контейнером. Це дозволяє монтувати файли в файлову систему кожного контейнера, схоже на завантаження файлів на вузол.

apiVersion: kind.x-k8s.io/v1alpha4
kind: Cluster
nodes:
- role: control-plane
  extraMounts:
  - hostPath: "./profiles"
    containerPath: "/var/lib/kubelet/seccomp/profiles"

Завантажте цей приклад конфігурації kind та збережіть його у файлі з назвою kind.yaml:

curl -L -O https://k8s.io/examples/pods/security/seccomp/kind.yaml

Ви можете встановити конкретну версію Kubernetes, встановивши образ контейнера вузла. Дивіться Вузли в документації kind щодо конфігурації для отримання більш детальної інформації з цього питання. Цей посібник передбачає, що ви використовуєте Kubernetes v1.31.

Як бета-функція, ви можете налаштувати Kubernetes на використання профілю, який обирає стандартне середовище виконання контейнерів, замість того, щоб використовувати Unconfined. Якщо ви хочете спробувати це, дивіться увімкнення використання RuntimeDefault як типового профілю за замовчуванням для всіх завдань перш ніж продовжувати.

Як тільки у вас буде конфігурація kind, створіть кластер kind з цією конфігурацією:

kind create cluster --config=kind.yaml

Після створення нового кластера Kubernetes ідентифікуйте контейнер Docker, що працює як одновузловий кластер:

docker ps

Ви повинні побачити вивід, який вказує на те, що контейнер працює з назвою kind-control-plane, подібний до:

CONTAINER ID        IMAGE                  COMMAND                  CREATED             STATUS              PORTS                       NAMES
6a96207fed4b        kindest/node:v1.18.2   "/usr/local/bin/entr…"   27 seconds ago      Up 24 seconds       127.0.0.1:42223->6443/tcp   kind-control-plane

Якщо спостерігати файлову систему цього контейнера, ви побачите, що тека profiles/ була успішно завантажена в стандартний шлях seccomp з kubelet. Використовуйте docker exec, щоб виконати команду в Podʼі:

# Змініть 6a96207fed4b на ідентифікатор контейнера, який ви знайдете в результаті "docker ps"
docker exec -it 6a96207fed4b ls /var/lib/kubelet/seccomp/profiles
audit.json  fine-grained.json  violation.json

Ви перевірили, що ці профілі seccomp доступні для kubelet, що працює всередині kind.

Створення Pod, що використовує стандартний профіль seccomp середовища виконання контейнерів

Більшість контейнерних середовищ надають типовий перелік системних викликів, які дозволені або заборонені. Ви можете використовувати ці стандартні налаштування для вашого робочого навантаження, встановивши тип seccomp у контексті безпеки Pod або контейнера на RuntimeDefault.

Ось маніфест Podʼа, який запитує профіль seccomp RuntimeDefault для всіх своїх контейнерів:

apiVersion: v1
kind: Pod
metadata:
  name: default-pod
  labels:
    app: default-pod
spec:
  securityContext:
    seccompProfile:
      type: RuntimeDefault
  containers:
  - name: test-container
    image: hashicorp/http-echo:1.0
    args:
    - "-text=just made some more syscalls!"
    securityContext:
      allowPrivilegeEscalation: false

Створіть цей Pod:

kubectl apply -f https://k8s.io/examples/pods/security/seccomp/ga/default-pod.yaml
kubectl get pod default-pod

Под повинен бути запущений успішно:

NAME        READY   STATUS    RESTARTS   AGE
default-pod 1/1     Running   0          20s

Видаліть Pod перед переходом до наступного розділу:

kubectl delete pod default-pod --wait --now

Створення Podʼа з профілем seccomp для аудиту системних викликів

Для початку, застосуйте профіль audit.json, який буде записувати всі системні виклики процесу, до нового Podʼа.

Ось маніфест для цього Podʼа:

apiVersion: v1
kind: Pod
metadata:
  name: audit-pod
  labels:
    app: audit-pod
spec:
  securityContext:
    seccompProfile:
      type: Localhost
      localhostProfile: profiles/audit.json
  containers:
  - name: test-container
    image: hashicorp/http-echo:1.0
    args:
    - "-text=just made some syscalls!"
    securityContext:
      allowPrivilegeEscalation: false

Створіть Pod у кластері:

kubectl apply -f https://k8s.io/examples/pods/security/seccomp/ga/audit-pod.yaml

Цей профіль не обмежує жодні системні виклики, тому Pod повинен успішно запуститися.

kubectl get pod audit-pod
NAME        READY   STATUS    RESTARTS   AGE
audit-pod   1/1     Running   0          30s

Щоб мати можливість взаємодіяти з цим точкою доступу, створіть Service NodePort, який дозволяє доступ до точки доступу зсередини контейнера панелі управління kind.

kubectl expose pod audit-pod --type NodePort --port 5678

Перевірте, який порт було призначено Service на вузлі.

kubectl get service audit-pod

Вивід буде схожим на:

NAME        TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
audit-pod   NodePort   10.111.36.142   <none>        5678:32373/TCP   72s

Тепер ви можете використовувати curl, щоб отримати доступ до цієї точки доступу зсередини контейнера панелі управління kind на порту, який було відкрито цим Service. Використовуйте docker exec, щоб виконати команду curl всередині контейнера панелі управління:

# Змініть 6a96207fed4b на ідентифікатор контейнера панелі управління та 32373 на номер порту, який ви побачили у "docker ps"
docker exec -it 6a96207fed4b curl localhost:32373
just made some syscalls!

Ви можете бачити, що процес працює, але які саме системні виклики він робить? Оскільки цей Pod працює у локальному кластері, ви повинні бачити їх у /var/log/syslog у вашій локальній системі. Відкрийте нове вікно термінала та використовуйте команду tail, щоб переглянути виклики від http-echo:

# Шлях до логу на вашому компʼютері може відрізнятися від "/var/log/syslog"
tail -f /var/log/syslog | grep 'http-echo'

Ви вже повинні бачити деякі логи системних викликів, зроблених http-echo, і якщо ви знову запустите curl всередині контейнера панелі управління, ви побачите більше записів у лозі.

Наприклад:

Jul  6 15:37:40 my-machine kernel: [369128.669452] audit: type=1326 audit(1594067860.484:14536): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=29064 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=51 compat=0 ip=0x46fe1f code=0x7ffc0000
Jul  6 15:37:40 my-machine kernel: [369128.669453] audit: type=1326 audit(1594067860.484:14537): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=29064 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=54 compat=0 ip=0x46fdba code=0x7ffc0000
Jul  6 15:37:40 my-machine kernel: [369128.669455] audit: type=1326 audit(1594067860.484:14538): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=29064 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=202 compat=0 ip=0x455e53 code=0x7ffc0000
Jul  6 15:37:40 my-machine kernel: [369128.669456] audit: type=1326 audit(1594067860.484:14539): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=29064 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=288 compat=0 ip=0x46fdba code=0x7ffc0000
Jul  6 15:37:40 my-machine kernel: [369128.669517] audit: type=1326 audit(1594067860.484:14540): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=29064 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=0 compat=0 ip=0x46fd44 code=0x7ffc0000
Jul  6 15:37:40 my-machine kernel: [369128.669519] audit: type=1326 audit(1594067860.484:14541): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=29064 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=270 compat=0 ip=0x4559b1 code=0x7ffc0000
Jul  6 15:38:40 my-machine kernel: [369188.671648] audit: type=1326 audit(1594067920.488:14559): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=29064 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=270 compat=0 ip=0x4559b1 code=0x7ffc0000
Jul  6 15:38:40 my-machine kernel: [369188.671726] audit: type=1326 audit(1594067920.488:14560): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=29064 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=202 compat=0 ip=0x455e53 code=0x7ffc0000

Ви можете почати розуміти системні виклики, необхідні для процесу http-echo, переглядаючи запис syscall= на кожному рядку. Хоча ці виклики навряд чи охоплюють усі системні виклики, які він використовує, це може слугувати основою для профілю seccomp для цього контейнера.

Видаліть Service та Pod перед переходом до наступного розділу:

kubectl delete service audit-pod --wait
kubectl delete pod audit-pod --wait --now

Створення Podʼа з профілем seccomp, що спричиняє порушення правил

Для демонстрації застосуйте до Podʼа профіль, який не дозволяє жодних системних викликів.

Ось маніфест для цієї демонстрації:

apiVersion: v1
kind: Pod
metadata:
  name: violation-pod
  labels:
    app: violation-pod
spec:
  securityContext:
    seccompProfile:
      type: Localhost
      localhostProfile: profiles/violation.json
  containers:
  - name: test-container
    image: hashicorp/http-echo:1.0
    args:
    - "-text=just made some syscalls!"
    securityContext:
      allowPrivilegeEscalation: false

Спробуйте створити Pod у кластері:

kubectl apply -f https://k8s.io/examples/pods/security/seccomp/ga/violation-pod.yaml

Pod буде створено, але виникне проблема. Якщо ви перевірите стан Podʼа, ви побачите, що його не вдалося запустити.

kubectl get pod violation-pod
NAME            READY   STATUS             RESTARTS   AGE
violation-pod   0/1     CrashLoopBackOff   1          6s

Як видно з попереднього прикладу, процес http-echo потребує досить багато системних викликів. У цьому випадку seccomp було налаштовано на помилку при будь-якому системному виклику, встановивши "defaultAction": "SCMP_ACT_ERRNO". Це надзвичайно безпечно, але робить неможливим виконання будь-яких значущих дій. Насправді ви хочете надати робочим навантаженням тільки ті привілеї, які їм потрібні.

Видаліть Pod перед переходом до наступного розділу:

kubectl delete pod violation-pod --wait --now

Створення Podʼа з профілем seccomp, що дозволяє лише необхідні системні виклики

Якщо ви подивитеся на профіль fine-grained.json, ви помітите деякі з системних викликів, які спостерігалися в syslog у першому прикладі, де профіль встановлював "defaultAction": "SCMP_ACT_LOG". Тепер профіль встановлює "defaultAction": "SCMP_ACT_ERRNO", але явно дозволяє набір системних викликів у блоці "action": "SCMP_ACT_ALLOW". Ідеально, якщо контейнер буде успішно працювати, і ви не побачите жодних повідомлень у syslog.

Маніфест для цього прикладу:

apiVersion: v1
kind: Pod
metadata:
  name: fine-pod
  labels:
    app: fine-pod
spec:
  securityContext:
    seccompProfile:
      type: Localhost
      localhostProfile: profiles/fine-grained.json
  containers:
  - name: test-container
    image: hashicorp/http-echo:1.0
    args:
    - "-text=just made some syscalls!"
    securityContext:
      allowPrivilegeEscalation: false

Створіть Pod у вашому кластері:

kubectl apply -f https://k8s.io/examples/pods/security/seccomp/ga/fine-pod.yaml
kubectl get pod fine-pod

Pod має успішно запуститися:

NAME        READY   STATUS    RESTARTS   AGE
fine-pod   1/1     Running   0          30s

Відкрийте нове вікно термінала і використовуйте tail для моніторингу записів лога, які згадують виклики від http-echo:

# Шлях до лога на вашому компʼютері може відрізнятися від "/var/log/syslog"
tail -f /var/log/syslog | grep 'http-echo'

Далі, опублікуйте Pod за допомогою Service NodePort:

kubectl expose pod fine-pod --type NodePort --port 5678

Перевірте, який порт було призначено для цього сервісу на вузлі:

kubectl get service fine-pod

Вихідні дані можуть виглядати приблизно так:

NAME        TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
fine-pod    NodePort   10.111.36.142   <none>        5678:32373/TCP   72s

Використовуйте curl для доступу до цієї точки доступу з контейнера панелі управління kind:

# Змініть 6a96207fed4b на ID контейнера панелі управління і 32373 на номер порту, який ви побачили у "docker ps"
docker exec -it 6a96207fed4b curl localhost:32373
just made some syscalls!

Ви не повинні побачити жодних записів у syslog. Це тому, що профіль дозволив усі необхідні системні виклики та вказав, що повинна статися помилка, якщо буде викликано виклик, який не входить до списку. Це ідеальна ситуація з погляду безпеки, але потребує певних зусиль для аналізу програми. Було б добре, якби існував простий спосіб наблизитися до такої безпеки без стількох зусиль.

Видаліть Service і Pod перед переходом до наступного розділу:

kubectl delete service fine-pod --wait
kubectl delete pod fine-pod --wait --now

Увімкнення використання RuntimeDefault як стандартного профілю seccomp для всіх робочих навантажень

СТАН ФУНКЦІОНАЛУ: Kubernetes v1.27 [stable]

Для використання стандартного профілю seccomp, необхідно запустити kubelet з параметром командного рядка --seccomp-default увімкненим для кожного вузла, де ви хочете його використовувати.

Якщо ця функція увімкнена, kubelet буде використовувати як типовий профіль seccomp RuntimeDefault, який визначений середовищем виконання контейнерів, замість використання режиму Unconfined (seccomp вимкнений). Типові профілі прагнуть забезпечити сильний набір налаштувань безпеки, зберігаючи функціональність робочих навантажень. Можливо, типові профілі відрізняються між середовищами виконання контейнерів та їх версіями випуску, наприклад, порівнюючи профілів CRI-O та containerd.

Деякі робочі навантаження можуть вимагати меншої кількості обмежень системних викликів, ніж інші. Це означає, що вони можуть зазнати невдачі під час виконання навіть із профілем RuntimeDefault. Для помʼякшення таких невдач можна:

  • Запустити робоче навантаження явно як Unconfined.
  • Вимкнути функцію SeccompDefault для вузлів, забезпечуючи, щоб робочі навантаження виконувалися на вузлах, де ця функція вимкнена.
  • Створити власний профіль seccomp для робочого навантаження.

Якщо ви вводите цю функцію до кластера подібного до операційного, проєкт Kubernetes рекомендує увімкнути цю функцію на підмножині ваших вузлів та перевірити виконання робочих навантажень перед повсюдним впровадженням змін.

Детальнішу інформацію про можливу стратегію оновлення та відкату ви можете знайти в повʼязаній пропозиції щодо поліпшення Kubernetes (KEP): Увімкнення seccomp стандартно.

Kubernetes 1.31 дозволяє налаштувати профіль seccomp, який застосовується, коли специфікація для Podʼа не визначає конкретний профіль seccomp. Однак, вам все одно потрібно увімкнути це налаштування як типове для кожного вузла, де ви хочете його використовувати.

Якщо ви працюєте у кластері Kubernetes 1.31 і хочете увімкнути цю функцію, ви можете запустити kubelet з параметром командного рядка --seccomp-default або увімкнути її через файл конфігурації kubelet. Щоб увімкнути цю функцію у kind, переконайтеся, що kind забезпечує мінімально необхідну версію Kubernetes і вмикає функцію SeccompDefault у конфігурації kind:

kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
  - role: control-plane
    image: kindest/node:v1.28.0@sha256:9f3ff58f19dcf1a0611d11e8ac989fdb30a28f40f236f59f0bea31fb956ccf5c
    kubeadmConfigPatches:
      - |
        kind: JoinConfiguration
        nodeRegistration:
          kubeletExtraArgs:
            seccomp-default: "true"        
  - role: worker
    image: kindest/node:v1.28.0@sha256:9f3ff58f19dcf1a0611d11e8ac989fdb30a28f40f236f59f0bea31fb956ccf5c
    kubeadmConfigPatches:
      - |
        kind: JoinConfiguration
        nodeRegistration:
          kubeletExtraArgs:
            seccomp-default: "true"        

Якщо кластер готовий, тоді запустіть Pod:

kubectl run --rm -it --restart=Never --image=alpine alpine -- sh

тепер він повинен мати прикріплений типовий профіль seccomp. Це можна перевірити, використовуючи docker exec для запуску crictl inspect для контейнера на вузлі kind:

docker exec -it kind-worker bash -c \
    'crictl inspect $(crictl ps --name=alpine -q) | jq .info.runtimeSpec.linux.seccomp'
{
  "defaultAction": "SCMP_ACT_ERRNO",
  "architectures": ["SCMP_ARCH_X86_64", "SCMP_ARCH_X86", "SCMP_ARCH_X32"],
  "syscalls": [
    {
      "names": ["..."]
    }
  ]
}

Що далі

Ви можете дізнатися більше про Linux seccomp: