Vodiči

Secure Your Docker Host: Rootless, Hardened, and Upgradable

Zaštitite svoj Docker poslužitelj: bez roota, ojačan i nadogradiv

Ojačani host za spremnike nije jednostavno običan poslužitelj s instaliranim Dockerom. Granica ovlasti demona, objavljeni portovi, vjerodajnice, zapisnici, sigurnosne kopije i put nadogradnje svi postaju dio produkcijskog dizajna.

Ovaj vodič izrađuje namjerno malen, ali cjelovit sustav: rootless Docker demon pokreće autentificiranu Nginx uslugu na portu 8080, dok nftables ograničava pristup odobrenim mrežama. Radno opterećenje ima ograničenja resursa, rotirane zapisnike, provjere stanja, zaštićenu tajnu, dosljedne sigurnosne kopije i testiran put vraćanja na prethodnu verziju.

Preduvjeti i radne pretpostavke

Koristite namjenski Debian ili Ubuntu host temeljen na systemd-u, s cgroup v2, stabilnom IP adresom, ispravnom sinkronizacijom vremena i administrativnim pristupom putem konzole ili postojeće SSH sesije. Primjeri pretpostavljaju da je službeni repozitorij Docker paketa već konfiguriran.

Trebat će vam dva stvarna mrežna raspona:

  • Administrativna mreža kojoj je dopušten pristup SSH-u.
  • Servisna mreža ili mreža obrnutog proxyja kojoj je dopušten pristup portu 8080.

Primjeri koriste 10.20.0.0/16 i 10.30.0.0/16. Zamijenite ih prije primjene vatrozida. Port 8080 prenosi obični HTTP, stoga ga izložite samo pouzdanoj privatnoj mreži, VPN-u ili obrnutom proxyju koji završava TLS. Nemojte ga objaviti izravno na nepouzdanoj internetskoj putanji.

Zadržite trenutačnu administrativnu sesiju otvorenom tijekom mijenjanja pravila vatrozida. Provjera sintakse ne može otkriti netočnu izvornu mrežu.

Arhitektura i kompromisi

Docker radi pod neprivilegiranim računom appsvc. Root spremnika mapiran je u podređeni raspon korisničkih ID-ova tog računa, a ne na root računala domaćina. Bijeg iz spremnika stoga se ne pretvara odmah u root kompromitiranje domaćina.

Rootless način rada ima svoju cijenu. Njegova putanja mreže u korisničkom prostoru može trošiti više CPU-a od izvornog bridge umrežavanja, portovi s malim brojevima zahtijevaju dodatni dizajn, a ograničenja resursa ovise o delegiranju cgroup v2. To su razumni kompromisi za interno web radno opterećenje, ali prije standardizacije ovog obrasca izmjerite usluge s velikim protokom ili osjetljive na latenciju.

Raspored na domaćinu namjerno je izričit:

/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

Instalirajte rootless runtime

Sljedeće pokrenite kao root na domaćinu. Namjenski račun nema lozinku i ne bi trebao dobiti širok sudo pristup.

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

Potvrdite da appsvc ima unose i u /etc/subuid i u /etc/subgid. Nemojte ručno izmišljati preklapajuće raspone; upotrijebite mehanizam za upravljanje računima primjeren distribuciji ako bilo koji unos nedostaje.

Pokrenite trajni korisnički upravitelj, zatim instalirajte Docker kao appsvc. Ove naredbe izdaje root, ali alat za postavljanje i demon rade pod servisnim računom:

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

Ako utičnica sabirnice nije prisutna, pokrenite korisnički upravitelj appsvc putem loginctl ili stvorite stvarnu sesiju prijave, zatim ponovite provjeru. Nemojte se vratiti na Docker utičnicu u vlasništvu roota samo kako biste zaobišli problem korisničke sesije.

Izradite radno opterećenje

Stvorite tajnu za autentifikaciju

Ovu i sve sljedeće Docker naredbe pokrenite kao appsvc. Lozinka se čita bez prikaza u terminalu i prosljeđuje OpenSSL-u putem standardnog ulaza, a ne kao argument naredbenog retka.

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 tajna nije trezor i nije šifrirana samo zato što se pojavljuje pod /run/secrets. Njezina je vrijednost u tome što vjerodajnica ostaje izvan slike, okruženja, Compose datoteke i argumenata procesa. Zaštitite njezinu datoteku na domaćinu i svaku sigurnosnu kopiju koja je sadržava.

Konfigurirajte Nginx

Stvorite /home/appsvc/edge-site/nginx/nginx.conf s ovom konfiguracijom:

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;
        }
    }
}

Stvorite site/index.html koji sadrži jednostavan odlomak poput <p>Rootless usluga je ispravna.</p>. U stvarnoj implementaciji ovaj direktorij može sadržavati generiranu dokumentaciju ili drugo web-mjesto samo za čitanje.

Definirajte spremnik

Stvorite /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

Usko ograničene sposobnosti slike postoje unutar rootless korisničkog imenskog prostora. Omogućuju Nginxu inicijalizaciju direktorija i odbacivanje privilegija, ali ne dodjeljuju odgovarajuće sposobnosti domaćina. Korijenski datotečni sustav samo za čitanje i ograničeni privremeni datotečni sustavi smanjuju i mogućnosti postojanosti i nekontrolirani rast diska.

Pokrenite uslugu:

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

Provedite vatrozid domaćina

Rootless Docker ne pruža zamjenu za politiku ulaznog prometa domaćina. Stvorite /etc/nftables.d/edge-host.nft kao root, nakon zamjene obje primjerene mreže:

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;
    }
}

Prije nastavka provjerite postojeće vlasništvo nftablesa, firewallda ili UFW-a. Više upravitelja vatrozida ne bi se trebalo natjecati za iste hookove. Provjerite fragment prije učitavanja:

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

Za postojanost osigurajte da /etc/nftables.conf sadrži točno jedan redak include "/etc/nftables.d/*.nft", provjerite cijelu datoteku s nft --check --file /etc/nftables.conf i omogućite nftables.service. Testirajte drugu SSH vezu iz odobrene administrativne mreže prije zatvaranja izvorne sesije.

Testirajte sigurnosne putanje i putanje kvara

S računala na dopuštenoj servisnoj mreži provjerite stanje, odbijanje i uspješnu autentifikaciju:

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/

Također testirajte s mreže izvan dopuštenog raspona; TCP veza trebala bi isteći ili biti odbačena. Zaustavite korisnički demon i potvrdite da usluga postaje nedostupna, zatim ga ponovno pokrenite s systemctl --user start docker. Na kraju ponovno pokrenite domaćina i provjerite da trajno pokretanje započinje i rootless demon i spremnik kojim upravlja politika ponovnog pokretanja.

Zapisivanje, vidljivost i kapacitet

Koristite docker compose logs --since 15m site za pristupni i pogrešni izlaz te journalctl --user -u docker --since today za događaje demona. Lokalni upravljački program za zapisivanje rotira zapisnike spremnika, ali rotacija nije centralizirano zadržavanje. Proslijedite odabrane zapisnike domaćina i aplikacije udaljenom sakupljaču ako istraga incidenta mora preživjeti gubitak domaćina.

Pratite stanje spremnika, broj ponovnih pokretanja, memoriju, broj procesa, korištenje datotečnog sustava i korisničku uslugu rootless demona. docker stats --no-stream pruža korisnu trenutačnu provjeru, a ne trajno praćenje.

Ako se ograničenja memorije ili CPU-a čine neučinkovitima, provjerite koristi li domaćin cgroup v2 i delegira li korisnički upravitelj kontrolere. Nemojte tiho ukloniti ograničenja: nedostupno provođenje tretirajte kao neuspjeh implementacije ili dokumentirajte izričitu alternativu za kontrolu kapaciteta.

Izradite i testirajte dosljedne sigurnosne kopije

Primjer je statičan, ali postupak izrade sigurnosne kopije zaustavlja radno opterećenje kako bi uspostavio jasnu granicu dosljednosti. Usluge koje koriste baze podataka trebale bi uobičajeno koristiti vlastiti mehanizam snimke ili izvoza umjesto kopiranja datoteka s podacima uživo.

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"

Pregledajte direktorij obnove i po završetku uklonite upravo taj direktorij. Kopirajte šifrirane sigurnosne kopije u zasebnu pohranu s neovisnim vjerodajnicama i zadržavanjem. Sigurnosna kopija koja ostaje samo na Docker domaćinu nije zaštita od gubitka domaćina.

Nadogradite uz automatsko vraćanje na prethodnu verziju

Promjenjiva oznaka stable-alpine čini kontrolirana ažuriranja praktičnima, dok prethodni lokalni ID slike pruža cilj za vraćanje na prethodnu verziju. Nadogradnje pokrenite iz direktorija projekta:

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

Za veći skup poslužitelja promičite testirane sažetke slika kroz okruženja umjesto da svakom domaćinu dopustite neovisno preuzimanje promjenjive oznake. Zadržite prethodnu sliku dok ne prođu testovi aplikacije i razdoblje promatranja.

Česti neuspjesi

  • Rootless demon nestaje nakon odjave: potvrdite da loginctl show-user appsvc prikazuje omogućeno trajno pokretanje i da je korisnička usluga omogućena.
  • Spremnik je lokalno ispravan, ali je daljinski nedostupan: pregledajte objavljenu adresu s docker port edge-site, zatim provjerite nftables brojače i izvornu adresu klijenta.
  • Nginx se neprestano ponovno pokreće: pregledajte njegove zapisnike. Tipični uzroci su nečitljiva tajna, neispravna konfiguracija ili nedostajuća putanja tmpfs-a u koju se može pisati.
  • Ograničenja se ignoriraju ili odbijaju: provjerite cgroup v2 i delegiranje korisničke usluge umjesto pretpostavke da rootless način rada može provesti kontrolere koji računu nisu dostupni.
  • Nadogradnja ne uspije prije testiranja stanja: sačuvajte stari ID slike i pokrenite dokumentirano vraćanje na prethodnu verziju. Nemojte uklanjati slike kao dio transakcije nadogradnje.

Završni kontrolni popis za provjeru

  • Docker demon radi kao appsvc, a ne root, i preživljava ponovno pokretanje.
  • Samo odobrene mreže mogu doseći SSH i port 8080.
  • Neautentificirani zahtjevi aplikaciji vraćaju HTTP 401.
  • Krajnja točka stanja uspijeva unutar ograničenog vremenskog ograničenja.
  • Spremnik ima korijenski datotečni sustav samo za čitanje, minimalne sposobnosti i ograničenja resursa.
  • Zapisnici se lokalno rotiraju, a važni događaji imaju plan vanjskog zadržavanja.
  • Postoji kontrolni zbroj sigurnosne kopije i obnova je uspješno raspakirana.
  • Prethodna slika ostaje dostupna i vraćanje na prethodnu verziju je uvježbano.

Ojačavanje najbolje funkcionira kada mijenja rutinske operacije, a ne samo zadane postavke instalacije. Rootless demon ograničava ovlasti, ali vatrozid ograničava izloženost; disciplina tajni ograničava curenje; rotacija ograničava pritisak na disk; sigurnosne kopije ograničavaju gubitak; a uvježbano vraćanje na prethodnu verziju ograničava rizik nadogradnje. Trajan rezultat nije neranjiv domaćin. To je domaćin čiji su kvarovi ograničeni, vidljivi i oporavljivi.

Portret autora bloga

Mihajlo

Ja sam Mihajlo — programer vođen znatiželjom, disciplinom i stalnom željom da stvorim nešto smisleno. Dijelim uvide, tutorijale i besplatne usluge kako bih pomogao drugima da pojednostave svoj rad i rastu u svijetu softvera i umjetne inteligencije koji se neprestano razvija.