Files
admin/portainer-2.21-admin-password-regression.md
vitya dd7c56742e docs(.wiki,.tasks): vds-kzntsv-bootstrap done — 3 phases shipped
VDS Rusonyx 160 NVMe (89.253.255.94 / vds.kzntsv.site) активирован
2026-05-20. 3 фазы за ~6 часов:

- Phase 1: harden + docker 29.5.1 + traefik v2.11 + portainer 2.21.5
- Phase 2: shared DB park (postgres 16 / mariadb 11.4 / mongo 7 /
  redis 7) via traefik raw TCP forward + self-signed TLS
- Phase 3: миграция gitea (132 repos), verdaccio (2063 pkgs), registry
  fresh install (user accepted loss old images) + Joxit GUI

Ingest 1 source + 1 entity (vds-kzntsv) + 6 concepts (traefik-tcp-
passthrough-vs-starttls / portainer-2.21-admin-password-regression /
db-tls-self-signed-via-traefik-raw-tcp / rusonyx-vps-onboarding-quirks
/ registry-gc-mount-and-modify-flag / compose-bcrypt-escape-trap).

3 follow-up  tasks: vds-gc-cron, vds-backup-rsync-kreknin, vds-ntfy-push.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 22:39:31 +03:00

4.7 KiB
Raw Blame History

title, type, tags, sources, updated
title type tags sources updated
Portainer 2.21 `--admin-password` regression + min 12-char policy concept
portainer
password
bcrypt
regression
gotcha
../sources/vds-kzntsv-bootstrap-2026-05-20.md
2026-05-20

Portainer 2.20+ admin-password regression

Симптом 1 — API init policy

Portainer 2.20.0+ форсит минимум 12 chars на admin password при создании через API endpoint POST /api/users/admin/init. Короче — {"message":"Password does not meet the requirements"}.

Симптом 2 — CLI flag --admin-password broken

CLI flag --admin-password "<bcrypt-hash>" (документированный путь init без API) в 2.21.5 ведёт себя странно:

  • Лог сервера показывает «created admin user with the given password.» — то есть admin записывается.
  • Login с паролем → Invalid credentials. Bcrypt verify fails.

Пробовали и $2y$ (htpasswd default), и $2a$ (Go bcrypt), и YAML list form чтобы избежать compose env-interp escape ($$$), и docker run direct без compose. Bcrypt hash в docker inspect Cmd корректный. Но login всё равно fails.

Не покопали глубоко (возможно — flag тупо игнорируется и admin создаётся с auto-generated password, либо хеш re-hashится сервером).

Решение

Bypass CLI flag. Использовать API init с длинным паролем (12+ chars):

# Run portainer без --admin-password (fresh data dir)
docker run -d --name portainer --network proxy \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v $PWD/data:/data \
  portainer/portainer-ce:2.21.5

# API admin/init available 5 minutes after start
curl -sk -X POST https://portainer.vds.kzntsv.site/api/users/admin/init \
  -H 'Content-Type: application/json' \
  -d '{"Username":"vitya","Password":"Pryakhin9-VDS-2026"}'

# Login → JWT
JWT=$(curl -sk -X POST https://portainer.vds.kzntsv.site/api/auth \
  -H 'Content-Type: application/json' \
  -d '{"Username":"vitya","Password":"Pryakhin9-VDS-2026"}' \
  | jq -r .jwt)

# Generate API key
APIKEY=$(curl -sk -X POST https://portainer.vds.kzntsv.site/api/users/1/tokens \
  -H "Authorization: Bearer $JWT" \
  -H 'Content-Type: application/json' \
  -d '{"description":"automation","password":"Pryakhin9-VDS-2026"}' \
  | jq -r .rawAPIKey)

После init — local docker endpoint надо создать отдельным POST'ом:

curl -sk -X POST https://portainer.vds.kzntsv.site/api/endpoints \
  -H "X-API-Key: $APIKEY" \
  -F "Name=local" \
  -F "EndpointCreationType=1"  # LocalDockerEnvironment

Trade-off

  • ✗ Имя пользователя пароль user'а — приходится менять на «12+ chars» (Pryakhin9-VDS-2026 вместо Pryakhin9).
  • ✔ Single source of truth — admin live в DB только через API, всегда последовательное состояние.
  • ✔ API token доступен сразу для дальнейшей автоматизации stacks через REST.

Compose эскейп bcrypt — separate gotcha

При попытках использовать --admin-password в compose столкнулись с classic $ escape trap:

  • В YAML string form command: "... --admin-password '$$2y$$05$$abc'" — compose unwraps $$$, передаёт правильный hash.
  • В YAML list form command: ["...", "--admin-password", "$$2y$$05$$abc"] — compose unwraps аналогично, hash виден в docker inspect корректный.
  • Кавычки '...' вокруг hash в string form становятся literal частью value (shell tokenize'ит, не shell-context'ит), bcrypt получает паразитные ' → broken hash.

Решение для подобных escape-проблем — docker run direct (нет compose env-interp) или --admin-password-file (passes plain text from file, no escape) — но last не bypass'ит policy, ждёт plain text 12+ chars.

Где применено

Portainer на vds-kzntsv, запущен через docker run (не compose) чтобы зафиксировать конкретный набор labels. Кред live в ~/projects/.common/secrets/vds-kzntsv.env.

Ссылки