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>
This commit is contained in:
85
portainer-2.21-admin-password-regression.md
Normal file
85
portainer-2.21-admin-password-regression.md
Normal file
@@ -0,0 +1,85 @@
|
||||
---
|
||||
title: Portainer 2.21 `--admin-password` regression + min 12-char policy
|
||||
type: concept
|
||||
tags: [portainer, password, bcrypt, regression, gotcha]
|
||||
sources: [../sources/vds-kzntsv-bootstrap-2026-05-20.md]
|
||||
updated: 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)**:
|
||||
|
||||
```bash
|
||||
# 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'ом:
|
||||
```bash
|
||||
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`](../entities/vds-kzntsv.md), запущен через `docker run` (не compose) чтобы зафиксировать конкретный набор labels. Кред live в `~/projects/.common/secrets/vds-kzntsv.env`.
|
||||
|
||||
## Ссылки
|
||||
|
||||
- Portainer changelog 2.20.0 — введён password policy + auth refactor (https://github.com/portainer/portainer/releases/tag/2.20.0).
|
||||
- Сессия где упоролись: [`vds-kzntsv-bootstrap-2026-05-20`](../sources/vds-kzntsv-bootstrap-2026-05-20.md) Phase 1 cont.
|
||||
Reference in New Issue
Block a user