Туториали

Secure Your Docker Host: Rootless, Hardened, and Upgradable

Обезбедете го вашиот Docker-домаќин: без root, зајакнат и надградлив

Зајакнат контејнерски хост не е едноставно обичен сервер со инсталиран Docker. Границата на привилегии на даемонот, објавените порти, акредитивите, дневниците, резервните копии и патеката за надградба стануваат дел од продукцискиот дизајн.

Ова упатство гради намерно мал, но комплетен систем: rootless Docker даемон извршува автентицирана Nginx услуга на портата 8080, додека nftables го ограничува пристапот до одобрени мрежи. Оптоварувањето има ограничувања на ресурси, ротирани дневници, здравствени проверки, заштитена тајна, конзистентни резервни копии и тестирана патека за враќање назад.

Предуслови и работни претпоставки

Користете посебен Debian или Ubuntu хост базиран на systemd, со cgroup v2, стабилна IP адреса, работна синхронизација на времето и административен пристап преку конзола или постоечка SSH сесија. Примерите претпоставуваат дека официјалното складиште на Docker пакети е веќе конфигурирано.

Ќе ви требаат два реални мрежни опсега:

  • Административна мрежа на која ѝ е дозволено да достигне SSH.
  • Услужна мрежа или мрежа на reverse proxy на која ѝ е дозволено да достигне порта 8080.

Примерите користат 10.20.0.0/16 и 10.30.0.0/16. Заменете ги пред да го примените заштитниот ѕид. Портата 8080 пренесува обичен HTTP, затоа изложете ја само на доверлива приватна мрежа, VPN или reverse proxy што завршува TLS. Не ја објавувајте директно на недоверлива интернет-патека.

Оставете ја тековната административна сесија отворена додека ги менувате правилата на заштитниот ѕид. Синтаксичката проверка не може да открие неточна изворна мрежа.

Архитектура и компромиси

Docker работи како непривилегираниот корисник appsvc. Root-корисникот во контејнерот е мапиран во подредениот опсег на кориснички ID-и на таа сметка, наместо во root-корисникот на хостот. Затоа, пробив од контејнерот не станува веднаш root-компромитација на хостот.

Rootless режимот има цена. Неговата мрежна патека во корисничкиот простор може да троши повеќе CPU од изворното bridge-вмрежување, портите со ниски броеви бараат дополнителен дизајн, а ограничувањата на ресурсите зависат од делегирање на cgroup v2. Ова се разумни компромиси за интерно веб-оптоварување, но направете benchmark на услуги со висок проток или чувствителни на латентност пред да го стандардизирате овој образец.

Распоредот на хостот е намерно експлицитен:

/home/appsvc/edge-site/
├── compose.yaml
├── nginx/
│   └── nginx.conf
└── site/
    └── index.html

/home/appsvc/.config/edge-site/
└── htpasswd

/home/appsvc/backups/
└── edge-site-TIMESTAMP.tar.zst

Инсталирајте го rootless runtime-от

Извршете го следново како root на хостот. Посебната сметка нема лозинка и не треба да добие широк sudo пристап.

apt-get update
apt-get install -y \
  docker-ce-cli docker-ce-rootless-extras docker-compose-plugin \
  uidmap dbus-user-session slirp4netns fuse-overlayfs \
  nftables openssl zstd curl

adduser --disabled-password --gecos "" appsvc
loginctl enable-linger appsvc

install -d -o appsvc -g appsvc -m 0750 \
  /home/appsvc/edge-site \
  /home/appsvc/edge-site/nginx \
  /home/appsvc/edge-site/site

install -d -o appsvc -g appsvc -m 0700 \
  /home/appsvc/.config/edge-site \
  /home/appsvc/backups

Потврдете дека appsvc има записи и во /etc/subuid и во /etc/subgid. Не измислувајте рачно преклопувачки опсези; користете го механизмот за управување со сметки што е соодветен за дистрибуцијата ако недостасува кој било запис.

Стартувајте го постојаниот кориснички менаџер, потоа инсталирајте Docker како appsvc. Овие команди ги издава root, но алатката за поставување и даемонот работат под услужната сметка:

app_uid="$(id -u appsvc)"
test -n "$app_uid"
test -S "/run/user/$app_uid/bus"

sudo -u appsvc \
  XDG_RUNTIME_DIR="/run/user/$app_uid" \
  dockerd-rootless-setuptool.sh check

sudo -u appsvc \
  XDG_RUNTIME_DIR="/run/user/$app_uid" \
  dockerd-rootless-setuptool.sh install

sudo -u appsvc \
  XDG_RUNTIME_DIR="/run/user/$app_uid" \
  systemctl --user enable --now docker

Ако bus socket-от не е присутен, стартувајте го корисничкиот менаџер на appsvc преку loginctl или креирајте реална сесија за најава, па повторете ја проверката. Не преминувајте на Docker socket во сопственост на root само за да заобиколите проблем со корисничка сесија.

Изградете го оптоварувањето

Креирајте ја тајната за автентикација

Извршете ја оваа и сите последователни Docker команди како appsvc. Лозинката се чита без ехо во терминалот и се предава на OpenSSL преку стандарден влез наместо како аргумент на командната линија.

cd /home/appsvc/edge-site
umask 077

IFS= read -r -s -p "Operator password: " site_password
printf '\n'
test -n "$site_password"

password_hash="$(
  printf '%s\n' "$site_password" | openssl passwd -6 -stdin
)"
unset site_password

printf 'operator:%s\n' "$password_hash" \
  > /home/appsvc/.config/edge-site/htpasswd
unset password_hash

chmod 0600 /home/appsvc/.config/edge-site/htpasswd

Compose тајна не е vault и не е шифрирана само затоа што се појавува под /run/secrets. Нејзината вредност е што акредитивот останува надвор од сликата, околината, Compose-датотеката и аргументите на процесот. Заштитете ја нејзината датотека на хостот и секоја резервна копија што ја содржи.

Конфигурирајте Nginx

Креирајте /home/appsvc/edge-site/nginx/nginx.conf со оваа конфигурација:

user nginx;
worker_processes auto;
error_log /dev/stderr info;
pid /run/nginx/nginx.pid;

events {
    worker_connections 1024;
}

http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    access_log /dev/stdout combined;
    sendfile on;
    keepalive_timeout 30;
    server_tokens off;

    server {
        listen 8080;
        root /usr/share/nginx/html;

        location = /healthz {
            access_log off;
            default_type text/plain;
            return 200 "ok\n";
        }

        location / {
            auth_basic "Restricted";
            auth_basic_user_file /run/nginx/htpasswd;
            try_files $uri $uri/ =404;
        }
    }
}

Креирајте site/index.html што содржи едноставен пасус, како <p>Rootless service is healthy.</p>. Во реално распоредување, оваа директорија може да содржи генерирана документација или друга страница само за читање.

Дефинирајте го контејнерот

Креирајте /home/appsvc/edge-site/compose.yaml:

services:
  site:
    image: docker.io/library/nginx:stable-alpine
    container_name: edge-site
    restart: unless-stopped
    ports:
      - "0.0.0.0:8080:8080"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./site:/usr/share/nginx/html:ro
    secrets:
      - basic_auth
    command:
      - /bin/sh
      - -ec
      - |
        cp /run/secrets/basic_auth /run/nginx/htpasswd
        chmod 0444 /run/nginx/htpasswd
        exec nginx -g 'daemon off;'
    read_only: true
    tmpfs:
      - /run/nginx:size=1m,mode=0755
      - /var/cache/nginx:size=16m,mode=0755
    security_opt:
      - no-new-privileges:true
    cap_drop:
      - ALL
    cap_add:
      - CHOWN
      - SETGID
      - SETUID
    pids_limit: 100
    mem_limit: 256m
    cpus: 1.0
    healthcheck:
      test: ["CMD", "wget", "-qO-", "http://127.0.0.1:8080/healthz"]
      interval: 30s
      timeout: 3s
      retries: 3
      start_period: 10s
    logging:
      driver: local
      options:
        max-size: "10m"
        max-file: "5"

secrets:
  basic_auth:
    file: /home/appsvc/.config/edge-site/htpasswd

Тесно ограничените способности на сликата постојат внатре во rootless корисничкиот namespace. Тие му дозволуваат на Nginx да иницијализира директориуми и да ги намали привилегиите, но не доделуваат соодветни способности на хостот. Root-датотечниот систем само за читање и ограничените привремени датотечни системи ги намалуваат и можностите за перзистенција и неконтролираниот раст на дискот.

Стартувајте ја услугата:

cd /home/appsvc/edge-site
docker compose config --quiet
docker compose pull
docker compose up -d
docker compose ps

Наметнете го заштитниот ѕид на хостот

Rootless Docker не обезбедува замена за политиката за влезен сообраќај на хостот. Креирајте /etc/nftables.d/edge-host.nft како root, откако ќе ги замените двете пример-мрежи:

table inet edge_host {
    chain input {
        type filter hook input priority 0;
        policy drop;

        ct state invalid drop
        ct state established,related accept
        iifname "lo" accept

        ip protocol icmp accept
        ip6 nexthdr ipv6-icmp accept

        ip saddr 10.20.0.0/16 tcp dport 22 accept
        ip saddr 10.30.0.0/16 tcp dport 8080 accept
    }

    chain forward {
        type filter hook forward priority 0;
        policy drop;
    }

    chain output {
        type filter hook output priority 0;
        policy accept;
    }
}

Проверете го постојното управување со nftables, firewalld или UFW пред да продолжите. Повеќе управувачи со заштитен ѕид не треба да се натпреваруваат за истите hooks. Потврдете го фрагментот пред да го вчитате:

nft --check --file /etc/nftables.d/edge-host.nft
nft --file /etc/nftables.d/edge-host.nft
nft list table inet edge_host

За перзистентност, осигурете се дека /etc/nftables.conf содржи точно една линија include "/etc/nftables.d/*.nft", потврдете ја целосната датотека со nft --check --file /etc/nftables.conf и овозможете nftables.service. Тестирајте втора SSH-врска од одобрената административна мрежа пред да ја затворите изворната сесија.

Тестирајте ги безбедносните патеки и патеките на откажување

Од машина на дозволената услужна мрежа, потврдете ги здравјето, одбивањето и успешната автентикација:

curl --fail --silent --show-error --max-time 2 \
  http://HOST_ADDRESS:8080/healthz

test "$(
  curl --silent --output /dev/null \
    --write-out '%{http_code}' \
    --max-time 2 \
    http://HOST_ADDRESS:8080/
)" = "401"

curl --fail --user operator \
  --max-time 5 \
  http://HOST_ADDRESS:8080/

Тестирајте и од мрежа надвор од дозволениот опсег; TCP-врската треба да истече или да биде отфрлена. Запрете го корисничкиот даемон и потврдете дека услугата станува недостапна, потоа рестартирајте го со systemctl --user start docker. Конечно, рестартирајте го хостот и потврдете дека lingering ги стартува и rootless даемонот и контејнерот управуван со restart.

Дневници, набљудливост и капацитет

Користете docker compose logs --since 15m site за пристапниот и излезот за грешки, и journalctl --user -u docker --since today за настаните на даемонот. Локалниот двигател за евидентирање ги ротира дневниците на контејнерот, но ротацијата не е централизирано задржување. Препраќајте избрани дневници на хостот и апликацијата до оддалечен собирач ако истрагата на инциденти мора да преживее губење на хостот.

Следете го здравјето на контејнерот, бројот на рестартирања, меморијата, бројот на процеси, користењето на датотечниот систем и корисничката услуга на rootless даемонот. docker stats --no-stream обезбедува корисна моментална проверка, а не трајно следење.

Ако ограничувањата на меморијата или CPU изгледаат неефикасни, потврдете дека хостот користи cgroup v2 и дека корисничкиот менаџер делегира контролери. Не отстранувајте ги ограничувањата тивко: третирајте го недостапното спроведување како неуспех при распоредувањето или документирајте експлицитна алтернатива за контрола на капацитетот.

Креирајте и тестирајте конзистентни резервни копии

Примерот е статичен, но постапката за резервна копија го запира оптоварувањето за да воспостави јасна граница на конзистентност. Услугите поддржани од база на податоци обично треба да го користат нивниот изворен механизам за snapshot или dump наместо да копираат датотеки со активни податоци.

cd /home/appsvc/edge-site
umask 077

backup_dir=/home/appsvc/backups
stamp="$(date -u +%Y%m%dT%H%M%SZ)"
archive="$backup_dir/edge-site-$stamp.tar.zst"

test -d "$backup_dir"
test ! -e "$archive"

docker compose stop
trap 'docker compose start' EXIT

tar --create --zstd --file="$archive" \
  compose.yaml nginx site \
  -C /home/appsvc .config/edge-site

sha256sum "$archive" > "$archive.sha256"

docker compose start
trap - EXIT

restore_dir="$(mktemp -d /home/appsvc/restore-test.XXXXXX)"
tar --extract --zstd --file="$archive" --directory="$restore_dir"
test -f "$restore_dir/compose.yaml"
test -f "$restore_dir/.config/edge-site/htpasswd"

Проверете ја директоријата за враќање и отстранете ја токму таа директорија кога ќе завршите. Копирајте ги шифрираните резервни копии во одделно складиште со независни акредитиви и задржување. Резервна копија што останува само на Docker хостот не е заштита од губење на хостот.

Надградете со автоматско враќање назад

Променливата ознака stable-alpine ги прави контролираните ажурирања практични, додека претходниот локален ID на сликата обезбедува цел за враќање назад. Извршувајте надградби од проектната директорија:

set -eu

cd /home/appsvc/edge-site
image_ref=docker.io/library/nginx:stable-alpine
old_image="$(docker inspect --format '{{.Image}}' edge-site)"
test -n "$old_image"

docker compose pull site

if ! docker compose up -d --force-recreate site; then
    docker tag "$old_image" "$image_ref"
    docker compose up -d --pull never --force-recreate site
    exit 1
fi

healthy=false
for attempt in $(seq 1 30); do
    if curl --fail --silent --output /dev/null --max-time 2 \
        http://127.0.0.1:8080/healthz; then
        healthy=true
        break
    fi
    sleep 2
done

if test "$healthy" != true; then
    docker compose logs --tail 100 site
    docker tag "$old_image" "$image_ref"
    docker compose up -d --pull never --force-recreate site
    exit 1
fi

docker image inspect "$old_image" > /dev/null
docker compose ps

За поголем кластер, промовирајте тестирани image digest-и низ околини наместо да дозволувате секој хост независно да повлекува променлива ознака. Задржете ја претходната слика додека не поминат тестовите на апликацијата и периодот на набљудување.

Вообичаени неуспеси

  • Rootless даемонот исчезнува по одјавувањето: потврдете дека loginctl show-user appsvc известува дека lingering е овозможен и дека корисничката услуга е овозможена.
  • Контејнерот е здрав локално, но недостижен оддалеку: проверете ја објавената адреса со docker port edge-site, потоа испитајте ги nftables броячите и изворната адреса на клиентот.
  • Nginx постојано се рестартира: проверете ги неговите дневници. Типични причини се нечитлива тајна, неправилно форматирана конфигурација или недостасувачка запишлива tmpfs патека.
  • Ограничувањата се игнорираат или одбиваат: проверете ги cgroup v2 и делегирањето на корисничката услуга наместо да претпоставувате дека rootless режимот може да спроведе контролери што не се достапни за сметката.
  • Надградбата не успева пред тестирањето на здравјето: зачувајте го стариот ID на сликата и извршете го документираното враќање назад. Не чистете слики како дел од трансакцијата за надградба.

Конечна контролна листа за потврда

  • Docker даемонот работи како appsvc, не како root, и преживува рестартирање.
  • Само одобрените мрежи можат да достигнат SSH и порта 8080.
  • Неавтентицираните барања до апликацијата враќаат HTTP 401.
  • Крајната точка за здравје успева со ограничен timeout.
  • Контејнерот има root-датотечен систем само за читање, минимални способности и ограничувања на ресурси.
  • Дневниците се ротираат локално, а важните настани имаат план за надворешно задржување.
  • Постои контролна сума за резервната копија и враќањето беше успешно распакувано.
  • Претходната слика останува достапна и враќањето назад е испробано.

Зајакнувањето најдобро функционира кога ги менува рутинските операции, а не само стандардните поставки при инсталацијата. Rootless даемонот ги ограничува привилегиите, но заштитниот ѕид ја ограничува изложеноста; дисциплината за тајни го ограничува протекувањето; ротацијата го ограничува притисокот врз дискот; резервните копии ја ограничуваат загубата; а увежбаното враќање назад го ограничува ризикот од надградба. Трајниот резултат не е неранлив хост. Тоа е хост чии неуспеси се ограничени, видливи и поправливи.

Портрет на автор на блогот

Mihajlo

Јас сум Михајло - развивач поттикнат од љубопитност, дисциплина и постојаната желба да создадам нешто значајно. Споделувам увиди, упатства и бесплатни услуги за да им помогнам на другите да ја поедностават својата работа и да растат во постојано развивачкиот свет на софтверот и вештачката интелигенција.