У Linux права root розділені на окремі можливості (capabilities): змінювати власника файлів (CAP_CHOWN), відкривати порти нижче 1024 (CAP_NET_BIND_SERVICE), налаштовувати мережу (CAP_NET_ADMIN), завантажувати модулі ядра (CAP_SYS_MODULE) тощо.
Docker за замовчуванням дає контейнеру обмежений набір - близько півтора десятка можливостей (CHOWN, SETUID, NET_BIND_SERVICE, KILL та інші), а небезпечні (SYS_ADMIN, NET_ADMIN, SYS_MODULE) прибирає. Тому root у контейнері слабший за root на хості.
Принцип найменших привілеїв - прибрати все й додати лише потрібне:
docker run --cap-drop ALL --cap-add NET_BIND_SERVICE nginx
services:
app:
cap_drop: [ALL]
cap_add: [NET_BIND_SERVICE]
security_opt:
- no-new-privileges:true
docker run --rm --cap-drop ALL alpine chown nobody /tmp
# chown: /tmp: Operation not permitted - навіть від root
no-new-privileges забороняє процесу отримати нові права через setuid-бінарні файли (наприклад, sudo, su) - навіть якщо вони є в образі.
Скільки можливостей потрібно типовому вебзастосунку? Часто - жодної, якщо він працює від звичайного користувача й слухає порт вище 1024. Процеси, що стартують від root і перемикаються на іншого користувача (nginx, php-fpm master), потребують SETUID, SETGID, CHOWN. Підібрати мінімальний набір - експериментально: прибрати все й додавати те, без чого контейнер не запускається.
Небезпечні можливості, які додають «щоб працювало»:
SYS_ADMIN- фактично майже повний root (монтування, багато адміністративних операцій) - один із класичних шляхів виходу з контейнера;NET_ADMIN- керування мережею хоста;SYS_PTRACE- відстеження інших процесів.
Якщо документація сторонньої програми вимагає таких можливостей, варто зрозуміти навіщо, а не додавати бездумно.
Перевірити ефективні можливості в контейнері: grep CapEff /proc/self/status і розшифрувати capsh --decode=....
Докладніше в документації: Запуск контейнерів: привілеї й можливості