Пошаговая инструкция для администратора Vault: от Userpass mount до проверки первого JWT через наше API.
Важно
Страница статическая: команды только копируются и не выполняются автоматически. Реальные credentials здесь не хранятся.
00 / CONTEXT
Два независимых доступа к Vault
Credentials приложения и внешнего API-пользователя никогда не смешиваются.
A
Доступ приложения
Node.js application
|
| VAULT_ROLE_ID + VAULT_SECRET_ID
v
Vault AppRole
|
v
Application Vault token
|
v
Internal application operations
чтение секретов;
JWT signing secret;
внутренние операции и health.
B
Доступ API-пользователя
External API user
|
| username + password
v
POST /api/v1/auth/token
|
v
Vault userpass: api-users
|
v
temporary user's Vault token
|
v
Node.js issues application JWT
backend отзывает временный Vault token;
клиент получает только JWT приложения.
Внимание
Пользователю API НЕ выдаются VAULT_ROLE_ID и VAULT_SECRET_ID приложения. Приложение НЕ использует свой AppRole для проверки пароля внешнего пользователя. Регистрация выполняется через публичный POST /api/v1/auth/register, а активация — вручную в Vault.
01 / CONTEXT
Где выполнять команды
В текущем Compose сервис Vault называется vault. Выполняйте команды из каталога docker:
cd docker
docker compose --profile vault exec vault vault status
Если вы уже внутри Vault container:
vault status
Проверка
Vault CLI уже есть внутри текущего контейнера; ставить CLI на host не требуется.
02 / PREFLIGHT
Предварительная проверка Vault
Состояние
vault status
InitializedtrueSealedfalse
Для работы Vault должен быть initialized и unsealed. Unseal keys не публикуются.
Auth methods
vault auth list
Ожидается api-users/ с типом userpass.
03 / BOOTSTRAP
Первичная настройка api-users
Важно
Выполняется один раз. Если api-users/ уже есть в vault auth list, enable повторно не выполняйте.
vault auth enable -path=api-users userpass
vault auth list
Не выполняйте vault auth disable api-users/ на рабочем Vault: это удаляет mount вместе с пользователями.
04 / NAMING
Правила username
Используйте letters, digits, _, - и .. Имя не начинается с - или . и не заканчивается ..
Не используйте @, /, пробелы и path-like usernames.
05 / AUTHORIZATION
Policies для API-пользователей
Сейчас policies из Vault переносятся в JWT claim policies. Middleware проверяет подпись, issuer, audience и exp; обязательная проверка конкретного policy name в текущем API не подключена.
Минимальная policy api-v1 не даёт доступа к application secrets:
Регистрация создаёт disabled Entity, Alias и Userpass user; активация выполняется вручную через Vault UI/CLI. Право create не позволяет менять пароль существующего user. Список пользователей берётся из Vault — MySQL хранит только JWT-состояние.
AppRole приложения → внутренние операции приложения. username/password api-users → аутентификация конкретного внешнего пользователя.
12 / JWT LIFECYCLE
Logout JWT
POST /api/v1/auth/exit с заголовком Authorization: Bearer <current-jwt> отзывает только текущий JWT. Он не меняет Vault password, не удаляет Vault user и не завершает другие JWT-сессии пользователя.
13 / SECURITY
Чек-лист безопасности
API users находятся в api-users, не в AppRole приложения.
AppRole используется только backend.
Не храните passwords в .env, MySQL или обычном KV.
Не вставляйте реальные passwords в shell commands — используйте read -s.
Не логируйте passwords, Authorization и Vault tokens.
Vault user token краткоживущий и отзывается backend после login.
JWT имеет короткий TTL и server-side session revocation.
Logout отзывает только текущий JWT.
BLOCKCHAIN BOUNDARY
TRON wallet architecture
Core отвечает только за API/Auth/Actions/Vault transport и orchestration. Вся TRON-specific логика находится в app/chains/tron/; TRON-код запрещено размещать в core services/controllers/actions, кроме вызова публичного интерфейса TRON-модуля.
Core
├─ API/Auth/Actions/Vault transport
└─ chains/
├─ tron/ <- вся TRON-specific логика
└─ ethereum/ <- будет добавлен позднее
Позднее app/chains/ethereum/ добавляется независимо от TRON и ядра.
Vault storage
tron-wallets/wallets/<TRON_ADDRESS> — KV v2 secret с CAS 0; policy AppRole:
Для signer не требуются update, delete, destroy или list. get_wallet_secrets возвращает критические секреты.
TRON MONITORING
Наблюдение, reconciliation и webhook
Core отвечает за API/JWT и orchestration; TRON-specific network, parsing, cursor и repositories находятся в app/chains/tron/. add_watch_wallet фиксирует confirmed head, baseline и новую session; active watch идемпотентен, reactivation начинает новую session и не восстанавливает transactions или balance changes inactive period. unwatch_wallet прекращает новые scans, но pending events сохраняются.
Два независимых cursor
Transaction ingestion читает confirmed blocks, локально сопоставляет TRX/официальный USDT со всеми active wallets и атомарно сохраняет transfers, reconciliation jobs и глобальный transaction cursor. Он никогда не запрашивает balance. При большом lag используется catch-up без overlap каждого batch; возле head — небольшой overlap с MySQL deduplication.
Balance reconciliation имеет постоянную MySQL-очередь и lease. Snapshot разрешён только после того, как transaction cursor догнал target job. Snapshot — текущее подтверждённое состояние, а не исторический баланс target block. Ошибки повторяются бесконечно с bounded backoff и не приводят к потере transfers.
Usage и workers
Production запускает один npm run worker:tron; TRON_EMBEDDED_WORKERS=false. Общий scheduler соблюдает RPS, приоритеты и учитывает каждый физический request по безопасному идентификатору ключа. Суточное окно задаётся TRON_API_DAILY_RESET_HOUR_UTC=16: лимит и прогноз сбрасываются ежедневно в 16:00 UTC. При 429 запрос сразу пробует следующий ключ, после блокировки всех ключей применяется максимальная пауза.
Immutable webhook
Payload новых batches содержит schema_version, монотонный sequence и batch_id. Успех — строго HTTP 200; семантика at-least-once требует deduplication по batch_id. После быстрых retries batch автоматически переходит в бесконечный slow retry и блокирует более новые sequence. Немедленный retry доступен кнопкой Dashboard и JWT endpoint POST /api/v1/tron/webhook_batches/{batch_uuid}/retry, не меняя payload, hash или состав.
HMAC остаётся HMAC-SHA256(timestamp + "." + exact_raw_json_body, TRON_WEBHOOK_SECRET). Retention удаляет chunks только delivered/completed данных. Dashboard и /health/monitoring показывают lag, balance queue, oldest webhook/blocking batch, daily usage и retention, не раскрывая keys, secret или payload.
Настройки
Все intervals, batch sizes, catch-up/overlap, reconciliation lease/retry, fast/slow webhook retry, RPS/daily budget/reset, retention и health thresholds перечислены с единицами в .env.example. Подробная внутренняя эксплуатационная документация: docs/tron-monitoring-internals.md.
WEBHOOK SECURITY
Проверка подписи на PHP
TRON_WEBHOOK_SECRET — общий секрет Core и webhook-получателя. Он доказывает, что запрос сформирован доверенной стороной и raw JSON body не изменился. Создайте случайное значение командой openssl rand -hex 32, настройте одинаковое значение на обеих сторонах и не сохраняйте его в репозитории, payload или логах.
Важно
Подпись нужно вычислять по исходной строке из php://input до json_decode(). Повторная сериализация JSON изменит байты и подпись не совпадёт.
<?php
$secret = getenv('TRON_WEBHOOK_SECRET') ?: '';
$rawBody = file_get_contents('php://input');
$timestamp = $_SERVER['HTTP_X_WEBHOOK_TIMESTAMP'] ?? '';
$signature = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';
$batchId = $_SERVER['HTTP_X_WEBHOOK_BATCH_ID'] ?? '';
if ($secret === '' || !ctype_digit($timestamp)) {
http_response_code(401);
exit('Invalid webhook configuration or timestamp');
}
// Защита от повторного использования старой подписи: допустимое окно 5 минут.
if (abs(time() - (int) $timestamp) > 300) {
http_response_code(401);
exit('Expired webhook timestamp');
}
$expected = 'sha256=' . hash_hmac(
'sha256',
$timestamp . '.' . $rawBody,
$secret
);
if (!hash_equals($expected, $signature)) {
http_response_code(401);
exit('Invalid webhook signature');
}
$payload = json_decode($rawBody, true, 512, JSON_THROW_ON_ERROR);
if (($payload['batch_id'] ?? '') !== $batchId || $batchId === '') {
http_response_code(400);
exit('Invalid batch id');
}
// Проверить в своей БД, не обработан ли уже $batchId.
// Сохранить batch_id и бизнес-изменения в одной локальной транзакции.
// Повтор того же batch_id должен завершаться без повторного применения событий.
http_response_code(200); // Только HTTP 200 подтверждает доставку для Core.
header('Content-Type: application/json');
echo json_encode(['accepted' => true, 'batch_id' => $batchId]);
Core использует at-least-once delivery: если HTTP 200 потеряется в сети, тот же immutable batch будет отправлен повторно. Поэтому получатель должен хранить обработанные batch_id и отвечать HTTP 200 также на уже успешно обработанный duplicate.