Укрепване на Docker контейнери: Rootless, Seccomp, AppArmor и Trivy

Практическо ръководство за укрепване на Docker контейнери в Linux с rootless режим, Seccomp профили, AppArmor, управление на capabilities и автоматизирано сканиране с Trivy. Актуално за 2026 г.

Защо сигурността на контейнерите е критична през 2026 г.

Docker контейнерите отдавна не са просто инструмент за разработчици — те са гръбнакът на продуктивната инфраструктура в хиляди организации. Но тъкмо тази повсеместност ги превръща в една от най-атакуваните повърхности. Само през 2025 г. бяха разкрити три критични уязвимости в runc (CVE-2025-31133, CVE-2025-52565 и CVE-2025-52881), позволяващи на атакуващия да избяга от контейнера и да получи root достъп до хоста. А през август 2025 г. Docker закърпи CVE-2025-9074 (CVSS 9.3) — уязвимост за пълен container escape чрез неавтентициран достъп до Docker Engine API.

Числата говорят сами за себе си: над 132 милиарда изтегляния на контейнерни образи в Docker Hub, като 54% от тях съдържат чувствителна информация или известни уязвимости. Да, повече от половината.

Атаките срещу build системи, непроверени образи от регистри и лошо конфигурирани Kubernetes деплойменти се увеличиха значително от началото на 2025 г. Ако сте следили предишните ни ръководства за укрепване на защитната стена с nftables, модерна SSH конфигурация с пост-квантова криптография и системи за откриване на проникване с auditd, AIDE и Wazuh, вече имате солидна защита на хост нивото. Но какво се случва, когато самите приложения живеят в контейнери?

Контейнерите споделят ядрото на хоста. Всяко компрометиране на контейнер е потенциална заплаха за цялата система.

В това ръководство ще изградим многослойна защита на Docker контейнери стъпка по стъпка — от rootless режим и управление на Linux capabilities, през Seccomp и AppArmor профили, до автоматизирано сканиране на образи с Trivy. Всички примери са тествани на Ubuntu 24.04 LTS и RHEL 9 с Docker Engine 27.x и Trivy v0.69.x към февруари 2026 г.

1. Rootless Docker — контейнери без root привилегии

Традиционно Docker демонът работи като root. Това означава, че ако атакуващ избяга от контейнера, веднага получава root достъп до хост системата. Честно казано, това е кошмарен сценарий, който съм виждал да се случва на практика.

Rootless режимът променя из основи този модел — и демонът, и контейнерите вървят като обикновен непривилегирован потребител.

1.1. Как работи rootless режимът

Rootless Docker използва потребителски пространства от имена (user namespaces) — функция на Linux ядрото, позволяваща непривилегирован потребител да създава контейнери. Вътре в пространството от имена потребителят изглежда като root, но извън него правата му са ограничени до обикновен потребител. Дори ако атакуващ успее да избяга от контейнера, получава достъп само до файловете на непривилегирования потребител — не до цялата система. Голяма разлика, нали?

1.2. Инсталация и настройка

Необходимо е Linux ядро 5.11+ (препоръчително) или 4.18+ с активирани user namespaces. Ето как да проверите:

# Проверка за поддръжка на user namespaces
cat /proc/sys/kernel/unprivileged_userns_clone
# Трябва да върне 1. Ако не:
sudo sysctl -w kernel.unprivileged_userns_clone=1

# Инсталиране на необходимите пакети
# Debian / Ubuntu
sudo apt install uidmap dbus-user-session fuse-overlayfs slirp4netns

# RHEL / Fedora / AlmaLinux
sudo dnf install shadow-utils fuse-overlayfs slirp4netns

След инсталацията на Docker Engine стартирайте rootless настройката:

# Настройка на rootless Docker (изпълнете като обикновен потребител, НЕ като root)
dockerd-rootless-setuptool.sh install

# Добавете в ~/.bashrc или ~/.zshrc
export PATH=/usr/bin:$PATH
export DOCKER_HOST=unix:///run/user/$(id -u)/docker.sock

# Активиране при стартиране на системата
systemctl --user enable docker
systemctl --user start docker

# Проверка — забележете, че демонът не е root
docker info | grep -i "root"
# Rootless: true

1.3. Конфигуриране на subuid/subgid

Rootless режимът изисква правилна конфигурация на подчинени UID/GID диапазони. Тази стъпка е лесна за пропускане, но без нея нищо няма да заработи:

# Проверка на текущата конфигурация
cat /etc/subuid
cat /etc/subgid

# Ако потребителят ви липсва, добавете го (например за потребител 'deploy'):
sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 deploy

# Потвърдете с:
id deploy
cat /etc/subuid | grep deploy
# deploy:100000:65536

1.4. Известни ограничения

Rootless Docker не е перфектен. Не може да използва портове под 1024 по подразбиране (което се решава с sysctl net.ipv4.ip_unprivileged_port_start=80). Мрежата минава през slirp4netns, което е по-бавно от стандартния bridge. Docker Swarm също не се поддържа.

Въпреки тези ограничения, за повечето продуктивни натоварвания rootless режимът е напълно подходящ и силно препоръчителен. Компромисът в производителността е минимален в сравнение с ползата от сигурността.

2. Управление на Linux capabilities — принципът на минималните привилегии

Linux capabilities разделят традиционните root привилегии на отделни единици, които могат да се дават или отнемат независимо. По подразбиране Docker дава на контейнерите подмножество от capabilities, но (и тук е уловката) дори този набор е повече, отколкото повечето приложения реално се нуждаят.

2.1. Стратегия: премахнете всичко, добавете само необходимото

# Най-сигурният подход — премахнете ВСИЧКИ capabilities и добавете само нужните
docker run --rm -it \
  --cap-drop ALL \
  --cap-add NET_BIND_SERVICE \
  --cap-add CHOWN \
  nginx:alpine

# Проверете текущите capabilities на работещ контейнер
docker exec  cat /proc/1/status | grep Cap
# CapPrm — разрешени, CapEff — ефективни

# Декодиране на capability маска
capsh --decode=00000000a80425fb

2.2. Важни capabilities и какво правят

Ето кои capabilities заслужават специално внимание:

  • NET_BIND_SERVICE — свързване към портове под 1024 (необходима за уеб сървъри)
  • CHOWN — промяна на собственост на файлове (необходима за някои процеси при стартиране)
  • SETUID / SETGID — промяна на потребител/група (необходима за процеси, които превключват потребител)
  • SYS_PTRACE — дебъгване на процеси (премахнете в продукция!)
  • NET_RAW — създаване на сурови сокети (необходима за ping, но лесна за злоупотреба)
  • SYS_ADMINникога не давайте тази capability — тя е на практика еквивалентна на root

2.3. Допълнителна защита с --security-opt=no-new-privileges

# Предотвратява ескалация на привилегии чрез setuid/setgid двоични файлове
docker run --rm -it \
  --cap-drop ALL \
  --cap-add NET_BIND_SERVICE \
  --security-opt=no-new-privileges \
  myapp:latest

Флагът --security-opt=no-new-privileges предотвратява получаването на нови привилегии от процеси вътре в контейнера. Дори ако бинарен файл има setuid бит, той няма да може да повиши привилегиите си. Проста мярка, но изключително ефективна — от типа, който трябва да е включен навсякъде по подразбиране.

3. Seccomp профили — защитна стена за системни повиквания

Seccomp (Secure Computing Mode) филтрира системните повиквания (syscalls), които процес може да прави. Мислете за него като за защитна стена, но не за мрежови пакети, а за взаимодействието между контейнера и ядрото. Docker прилага Seccomp профил по подразбиране, който блокира около 44 от 300+ системни повиквания.

3.1. Проверка на Seccomp поддръжка

# Проверка дали ядрото поддържа seccomp
grep CONFIG_SECCOMP= /boot/config-$(uname -r)
# CONFIG_SECCOMP=y

# Проверка дали Docker използва seccomp
docker info | grep -i seccomp
# Security Options: ... seccomp

# Проверка на seccomp статуса на работещ контейнер
docker inspect  | grep -A5 SecurityOpt

3.2. Профилът по подразбиране и защо не е достатъчен

Стандартният Docker seccomp профил е добра начална точка — блокира опасни syscalls като mount, reboot, kexec_load и unshare. Именно блокирането на unshare спря експлоатацията на CVE-2022-0185, уязвимост в ядрото. Но за приложения с конкретен профил на използване, персонализираният профил е далеч по-ефективен.

С други думи — стандартният профил е като брава на входната врата. Работи, но не е достатъчен за банков трезор.

3.3. Създаване на персонализиран Seccomp профил

Първо, анализирайте кои syscalls реално използва вашето приложение:

# Стартирайте контейнера без seccomp ограничения и със strace
docker run --rm -it --security-opt seccomp=unconfined \
  strace -c -f -S name myapp:latest 2>&1 | tail -50

# Или използвайте oci-seccomp-bpf-hook за автоматично генериране
# (наличен на Fedora, Ubuntu, openSUSE)
sudo apt install golang-github-containers-common
# Стартирайте контейнера с hook — профилът ще се генерира автоматично

След това създайте персонализиран профил в JSON формат. Ето примерна структура:

{
  "defaultAction": "SCMP_ACT_ERRNO",
  "architectures": ["SCMP_ARCH_X86_64", "SCMP_ARCH_X86", "SCMP_ARCH_AARCH64"],
  "syscalls": [
    {
      "names": [
        "accept", "accept4", "access", "bind", "brk", "clone",
        "close", "connect", "dup", "dup2", "epoll_create",
        "epoll_ctl", "epoll_wait", "execve", "exit", "exit_group",
        "fcntl", "fstat", "futex", "getdents64", "getpid",
        "getsockname", "getsockopt", "ioctl", "listen", "lseek",
        "madvise", "mmap", "mprotect", "munmap", "nanosleep",
        "open", "openat", "pipe", "poll", "read", "readlink",
        "recvfrom", "recvmsg", "rt_sigaction", "rt_sigprocmask",
        "sendmsg", "sendto", "setsockopt", "shutdown", "socket",
        "stat", "write", "writev"
      ],
      "action": "SCMP_ACT_ALLOW"
    }
  ]
}

Приложете профила при стартиране на контейнера:

# Стартиране с персонализиран seccomp профил
docker run --rm -it \
  --security-opt seccomp=/etc/docker/seccomp/myapp-profile.json \
  myapp:latest

# За Kubernetes — използвайте securityContext
# apiVersion: v1
# kind: Pod
# spec:
#   containers:
#   - name: myapp
#     securityContext:
#       seccompProfile:
#         type: Localhost
#         localhostProfile: profiles/myapp-profile.json

4. AppArmor профили за контейнери — контрол на достъпа до файлове

Докато Seccomp филтрира какви системни повиквания може да прави процесът, AppArmor контролира до какви ресурси има достъп — файлове, директории, мрежови операции, Linux capabilities. Docker прилага профил по подразбиране (docker-default), но персонализираните профили дават значително по-добра защита.

И тук си заслужава усилието, повярвайте ми.

4.1. Проверка на AppArmor статус

# Проверка дали AppArmor е активен
sudo aa-status

# Проверка на AppArmor профила на работещ контейнер
docker inspect  | grep -i apparmor
# "AppArmorProfile": "docker-default"

4.2. Създаване на персонализиран AppArmor профил

Ето примерен профил за nginx, който ограничава достъпа само до необходимите файлове и операции:

# /etc/apparmor.d/docker-nginx
#include <tunables/global>

profile docker-nginx flags=(attach_disconnected,mediate_deleted) {
  #include <abstractions/base>
  #include <abstractions/nameservice>

  # Мрежов достъп
  network inet tcp,
  network inet udp,
  network inet6 tcp,
  network inet6 udp,

  # Достъп за четене на конфигурация и статични файлове
  /etc/nginx/** r,
  /usr/share/nginx/** r,
  /var/www/html/** r,

  # Достъп за писане само в лог директорията и временни файлове
  /var/log/nginx/** rw,
  /var/cache/nginx/** rw,
  /run/nginx.pid rw,
  /tmp/** rw,

  # Изпълними файлове
  /usr/sbin/nginx ix,

  # Забрана на достъп до чувствителни системни файлове
  deny /etc/shadow r,
  deny /etc/passwd w,
  deny /proc/*/mem r,
  deny /sys/** w,

  # Забрана на mount операции
  deny mount,

  # Capabilities — само необходимите
  capability net_bind_service,
  capability setuid,
  capability setgid,
  capability dac_override,
}

Заредете и приложете профила:

# Парсиране и зареждане на профила
sudo apparmor_parser -r -W /etc/apparmor.d/docker-nginx

# Стартиране на контейнер с персонализирания профил
docker run --rm -d \
  --security-opt apparmor=docker-nginx \
  --cap-drop ALL \
  --cap-add NET_BIND_SERVICE \
  --cap-add CHOWN \
  --cap-add SETUID \
  --cap-add SETGID \
  nginx:alpine

# Проверка — опитайте да прочетете /etc/shadow от контейнера
docker exec  cat /etc/shadow
# cat: /etc/shadow: Permission denied — профилът работи!

4.3. Режим на обучение (complain mode)

Преди да включите enforcing режим, препоръчвам да стартирате профила в complain mode. Така ще регистрирате нарушенията, без да блокирате нищо — полезно е да видите какво реално прави приложението ви:

# Превключване в complain режим
sudo aa-complain /etc/apparmor.d/docker-nginx

# Стартирайте приложението и тествайте всички функции
# Прегледайте логовете за нарушения
sudo dmesg | grep ALLOWED | tail -20

# Когато сте готови — превключете обратно в enforce
sudo aa-enforce /etc/apparmor.d/docker-nginx

5. Сканиране на контейнерни образи с Trivy

Досега говорихме за защита по време на изпълнение. Но сигурността на контейнерите започва още преди стартирането — при изграждането и избора на базови образи. Trivy (от Aqua Security) е най-популярният инструмент с отворен код за сканиране на контейнерни образи, с над 32 000 звезди в GitHub и поддръжка на множество формати.

Ако все още не го използвате, сега е моментът.

5.1. Инсталация на Trivy

# Debian / Ubuntu
sudo apt install wget apt-transport-https gnupg lsb-release
wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | \
  gpg --dearmor | sudo tee /usr/share/keyrings/trivy.gpg > /dev/null
echo "deb [signed-by=/usr/share/keyrings/trivy.gpg] https://aquasecurity.github.io/trivy-repo/deb \
  $(lsb_release -sc) main" | sudo tee /etc/apt/sources.list.d/trivy.list
sudo apt update && sudo apt install trivy

# RHEL / Fedora / AlmaLinux
sudo rpm --import https://aquasecurity.github.io/trivy-repo/rpm/public.key
cat << EOF | sudo tee /etc/yum.repos.d/trivy.repo
[trivy]
name=Trivy repository
baseurl=https://aquasecurity.github.io/trivy-repo/rpm/releases/\$basearch/
gpgcheck=1
enabled=1
gpgkey=https://aquasecurity.github.io/trivy-repo/rpm/public.key
EOF
sudo dnf install trivy

# Проверка на версията (v0.69.x към февруари 2026)
trivy --version

5.2. Сканиране на образи

# Базово сканиране на образ
trivy image nginx:latest

# Само HIGH и CRITICAL уязвимости (препоръчително за CI/CD)
trivy image --severity HIGH,CRITICAL nginx:latest

# Само уязвимости с налични поправки (най-полезно за приоритизиране)
trivy image --severity HIGH,CRITICAL --ignore-unfixed nginx:latest

# Сканиране на локално изграден образ
docker build -t myapp:latest .
trivy image myapp:latest

# Сканиране на файлова система (без Docker образ)
trivy fs --scanners vuln,secret,misconfig .

5.3. Генериране на SBOM (Software Bill of Materials)

Trivy v0.69 поддържа генериране на SBOM в CycloneDX и SPDX формат. Това е критично за проследяване на зависимости и бърза реакция при инциденти като Log4Shell — когато знаете какво точно има в образите ви, можете да реагирате за минути, а не за дни:

# Генериране на CycloneDX SBOM
trivy image --format cyclonedx --output sbom.json nginx:latest

# Генериране на SPDX SBOM
trivy image --format spdx-json --output sbom-spdx.json nginx:latest

# Повторно сканиране на SBOM (по-бързо от пълно сканиране на образ)
trivy sbom sbom.json

5.4. Интеграция с CI/CD (GitHub Actions)

Ето работещ пример за GitHub Actions workflow, който автоматично сканира образите при всеки push или pull request:

# .github/workflows/container-security.yml
name: Container Security Scan
on:
  push:
    branches: [main]
  pull_request:

jobs:
  trivy-scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Build Docker image
        run: docker build -t myapp:${{ github.sha }} .

      - name: Run Trivy vulnerability scanner
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: myapp:${{ github.sha }}
          format: sarif
          output: trivy-results.sarif
          severity: CRITICAL,HIGH
          exit-code: 1

      - name: Upload Trivy scan results to GitHub Security
        uses: github/codeql-action/upload-sarif@v3
        if: always()
        with:
          sarif_file: trivy-results.sarif

5.5. Сканиране на Dockerfile за грешки в конфигурацията

Trivy може да сканира и самия Dockerfile за лоши практики — нещо, което мнозина пропускат:

# Сканиране на Dockerfile директно
trivy config ./Dockerfile

# Примерни открития:
# - Използване на root потребител
# - Липсващ HEALTHCHECK
# - Използване на 'latest' таг вместо фиксирана версия
# - Копиране на чувствителни файлове (.env, ключове)

6. Укрепен Dockerfile — събиране на всичко заедно

Хубаво, вече имаме отделните инструменти. Нека ги приложим заедно в един практически пример. Ето укрепен Dockerfile за Node.js приложение, който следва всички принципи, за които говорихме:

# Използвайте минимален базов образ с фиксирана версия
FROM node:22-alpine3.20 AS builder

WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production && npm cache clean --force
COPY . .

# Втори етап — минимален runtime образ
FROM node:22-alpine3.20

# Създаване на непривилегирован потребител
RUN addgroup -g 1001 appgroup && \
    adduser -u 1001 -G appgroup -s /bin/sh -D appuser

WORKDIR /app

# Копиране само на необходимите файлове
COPY --from=builder --chown=appuser:appgroup /app/node_modules ./node_modules
COPY --from=builder --chown=appuser:appgroup /app/dist ./dist
COPY --from=builder --chown=appuser:appgroup /app/package.json ./

# Превключване към непривилегирован потребител
USER appuser

# Healthcheck
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
  CMD wget -q --spider http://localhost:3000/health || exit 1

EXPOSE 3000

CMD ["node", "dist/server.js"]

И финалната команда за стартиране с пълния набор от защити:

docker run -d \
  --name myapp \
  --cap-drop ALL \
  --cap-add NET_BIND_SERVICE \
  --security-opt=no-new-privileges \
  --security-opt seccomp=/etc/docker/seccomp/myapp-profile.json \
  --security-opt apparmor=docker-myapp \
  --read-only \
  --tmpfs /tmp:rw,noexec,nosuid,size=64m \
  --memory=512m \
  --cpus=1.0 \
  --pids-limit=100 \
  --restart unless-stopped \
  myapp:latest

Изглежда дълго, но всеки флаг тук има конкретна роля за защитата. Препоръчвам да го запазите като шаблон и да го адаптирате за различните си приложения.

7. Контролен списък за сигурност на Docker контейнери

За да е по-лесно за запомняне, ето обобщение на всички мерки от ръководството:

  • Rootless Docker — стартирайте демона без root привилегии
  • --cap-drop ALL — премахнете всички capabilities по подразбиране
  • --cap-add — добавете само конкретно необходимите
  • --security-opt=no-new-privileges — забранете ескалация на привилегии
  • Seccomp профил — персонализиран или поне стандартният
  • AppArmor профил — ограничете достъпа до файлове и ресурси
  • --read-only — файловата система само за четене
  • USER — никога не стартирайте като root в контейнера
  • Trivy сканиране — автоматично в CI/CD за всеки образ
  • Минимален базов образ — Alpine или distroless
  • Фиксирани версии — никога не използвайте :latest в продукция
  • Multi-stage build — разделете build и runtime етапите
  • Ограничения на ресурси--memory, --cpus, --pids-limit
  • HEALTHCHECK — задължителен за всеки контейнер

Често задавани въпроси

Какъв е рискът от стартиране на Docker контейнер като root?

Когато контейнер работи като root и атакуващ успее да избяга от него, той получава пълен root достъп до хост системата. Това означава достъп до всички файлове, възможност за инсталиране на зловреден софтуер, промяна на системни настройки и компрометиране на други контейнери на същия хост. Rootless режимът и --cap-drop ALL минимизират тези рискове значително.

Каква е разликата между Seccomp и AppArmor за Docker контейнери?

Seccomp филтрира кои системни повиквания може да прави процесът — като защитна стена между контейнера и ядрото. AppArmor пък контролира до кои ресурси има достъп — файлове, директории, мрежови операции. Те работят на различни нива и се допълват взаимно. Най-добрата практика е да използвате и двете едновременно.

Колко често трябва да сканирам контейнерните си образи с Trivy?

Сканирайте при всеки build в CI/CD конвейера (блокирайте merge при CRITICAL/HIGH уязвимости), ежедневно за вече деплойнати образи (нови CVE се публикуват постоянно) и след всяко обновяване на базовия образ. Trivy поддържа SBOM-базирано повторно сканиране, което е значително по-бързо от пълно сканиране.

Мога ли да използвам SELinux вместо AppArmor за Docker контейнери?

Да, особено ако сте на RHEL-базирана дистрибуция, където SELinux е стандарт. Docker поддържа и двете — използвайте --security-opt label=type:container_runtime_t за SELinux или --security-opt apparmor=profile-name за AppArmor. SELinux предлага по-гранулиран контрол с етикети, но е по-сложен за конфигуриране. AppArmor е по-лесен за управление с файлови пътища.

Какво е SBOM и защо е важен за сигурността на контейнерите?

SBOM (Software Bill of Materials) е подробен списък на всички софтуерни компоненти и зависимости в контейнерния образ. Той е критичен за бърза реакция при инциденти — когато се открие нова уязвимост (като Log4Shell), SBOM позволява моментално да определите кои от образите ви са засегнати, без да ги сканирате наново. Trivy генерира SBOM в стандартните формати CycloneDX и SPDX.

За Автора Editorial Team

Our team of expert writers and editors.