Модуль 13 / 18
Безпека: rootless, capabilities, seccomp і LSM
Зміст
1. Цілі та модель загроз
Після модуля ви зможете застосовувати least privilege шарами та діагностувати відмову без небезпечного --privileged.
- non-root зменшує наслідки
- capabilities дроблять права root
- seccomp обмежує syscalls
- AppArmor/SELinux обмежують доступ
- read-only rootfs і no-new-privileges звужують зміни
2. Теорія: межа контейнера
Namespaces і cgroups не роблять container рівним VM: процеси поділяють kernel attack surface. Docker daemon і docker.sock зазвичай дають root-equivalent контроль host.
--privileged видає capabilities, devices і послаблює LSM, тому не виправляє Permission denied.
3. Теорія: user та user namespaces
USER або --user задає UID/GID процесу, але root звичайного container лишається UID 0 host у своєму namespace. Bind permissions числові.
userns-remap відображає root у subordinate UID. Rootless запускає daemon без host root, але має окремий context та обмеження network/storage/cgroup.
4. Теорія: Linux capabilities
Docker зберігає обмежений default-набір. Починайте з --cap-drop ALL і додавайте одну доведену capability. NET_RAW дозволяє raw sockets, NET_ADMIN змінює network, SYS_ADMIN надто широка.
Capability не скасовує seccomp, LSM і filesystem permissions: діє перетин шарів.
5. Теорія: seccomp і no-new-privileges
Default seccomp profile дозволяє сумісні syscalls і повертає EPERM для небезпечних. Не використовуйте unconfined без аналізу syscall.
no-new-privileges забороняє отримати додаткові privileges через execve, setuid або file capabilities.
6. Теорія: AppArmor, SELinux і filesystem
Docker застосовує docker-default AppArmor або SELinux labels. Denial шукають в audit, а не обходять вимкненням LSM.
Read-only rootfs потребує точкових writable mounts для /tmp, cache, sockets і даних; noexec/nosuid зменшують ризик.
7. Методичні вказівки
- Почніть з threat model
- Використовуйте non-root numeric UID
- Drop ALL, потім мінімальний add
- Read-only rootfs та точкові mounts
- Забороняйте privilege escalation
- Не монтуйте docker.sock і host root
- Фіксуйте HostConfig
- Тестуйте негативні сценарії
8. Лабораторна робота
Порівняйте default і hardened containers за UID, CapEff, seccomp та HostConfig.
docker info --format '{{json .SecurityOptions}}' | jq
docker run --name docker-m13-default alpine:3.21 sh -c 'id; grep -E "^(CapEff|CapBnd|NoNewPrivs|Seccomp):" /proc/1/status; cat /proc/1/attr/current 2>/dev/null || true; touch /root/default-write'
docker inspect docker-m13-default --format '{{json .HostConfig}}' | jq '{Privileged,ReadonlyRootfs,CapAdd,CapDrop,SecurityOpt,NetworkMode}'docker run --name docker-m13-hardened --user 65532:65532 --read-only --tmpfs /tmp:rw,noexec,nosuid,size=16m --cap-drop ALL --security-opt no-new-privileges --pids-limit 64 --network none alpine:3.21 sh -c 'id; grep -E "^(CapEff|CapBnd|NoNewPrivs|Seccomp):" /proc/1/status; touch /tmp/allowed; if touch /etc/forbidden 2>/dev/null; then exit 1; else echo rootfs_read_only=enforced; fi; test ! -e /proc/net/route || cat /proc/net/route'
docker inspect docker-m13-hardened --format '{{json .HostConfig}}' | jq '{Privileged,ReadonlyRootfs,CapAdd,CapDrop,SecurityOpt,NetworkMode,PidsLimit}'
docker diff docker-m13-hardenedДоведіть дію CHOWN та default seccomp.
docker run --rm alpine:3.21 sh -c 'touch /tmp/item; chown 1000:1000 /tmp/item; stat -c "uid=%u gid=%g" /tmp/item'
docker run --rm --cap-drop ALL alpine:3.21 sh -c 'touch /tmp/item; if chown 1000:1000 /tmp/item 2>/dev/null; then exit 1; else echo chown_capability_removed; fi'
docker run --rm --cap-drop ALL --cap-add CHOWN alpine:3.21 sh -c 'touch /tmp/item; chown 1000:1000 /tmp/item; stat -c "uid=%u gid=%g" /tmp/item'docker run --rm alpine:3.21 sh -c 'grep "^Seccomp:" /proc/1/status; if unshare --user true 2>/dev/null; then echo unexpected; else echo unshare_denied; fi'
docker inspect docker-m13-hardened --format 'apparmor={{.AppArmorProfile}} security={{json .HostConfig.SecurityOpt}}'Критерій: non-root, CapEff=0, NoNewPrivs=1, read-only, network none; точковий CHOWN дозволяє зміну власника.
9. Діагностика Permission denied
Відмову може дати DAC, capability, seccomp, LSM, read-only mount або device cgroup. Визначте шар до зміни policy.
docker inspect docker-m13-hardened --format '{{json .State}}' | jq
docker inspect docker-m13-hardened --format '{{json .HostConfig}}' | jq
docker logs docker-m13-hardened
docker events --since 10m --until 0s --filter container=docker-m13-hardened
journalctl -k --since '10 minutes ago' --no-pager | grep -Ei 'apparmor|selinux|seccomp|denied' | tail -30 || true- id/stat — identity та mode
- CapEff/CapBnd — capability
- Seccomp/SecurityOpt — syscall policy
- AppArmorProfile/audit — LSM
- Mounts/ReadonlyRootfs — filesystem
- devices та namespaces — exposure
10. Контроль і cleanup
- Чому container root не безпечний?
- Rootless чи --user?
- Чому docker.sock root-equivalent?
- Навіщо drop ALL?
- Що робить seccomp?
- Що забороняє no-new-privileges?
- Як знайти LSM denial?
- Чому не --privileged?
docker rm docker-m13-default docker-m13-hardened
docker ps -a --filter name=docker-m13Модуль засвоєно, якщо ви видаєте лише доведено потрібні права й локалізуєте denial за шарами.