feat: security audit fixes
This commit is contained in:
133
README.md
133
README.md
@@ -1,67 +1,114 @@
|
||||
# CryptoWallet API — Deployment Bundle (v5.0 custodial)
|
||||
# CryptoWallet API — Deployment Bundle
|
||||
|
||||
Multi-chain **custodial** wallet API (ETH / BSC / BTC / TRX / SOL).
|
||||
- Сервер генерит mnemonic, хранит зашифрованной (AES-256-GCM, master-key из HashiCorp Vault)
|
||||
- Сервер сам подписывает tx по запросу юзера (юзер на клиенте жмёт "подтвердить")
|
||||
|
||||
Auth — JWT (BITOK), секреты — HashiCorp Vault (AppRole).
|
||||
Auth — JWT, выданный сервисом **bitok** (внешний). Секреты — HashiCorp Vault (AppRole).
|
||||
|
||||
## Pre-deploy setup (один раз навсегда)
|
||||
## Pre-deploy setup (один раз)
|
||||
|
||||
```bash
|
||||
# 1. Master-key в Vault
|
||||
vault kv put dev-secrets/crypto/master key=$(openssl rand -hex 32)
|
||||
|
||||
# 2. DB schema
|
||||
psql -h 72.56.9.76 -U postgres_user -d postgres -f cryptowallet-schema.sql
|
||||
# 2. CSRF secret в Vault
|
||||
vault kv put dev-secrets/csrf secret_key=$(openssl rand -hex 32) salt=csrf-salt digest=sha256
|
||||
|
||||
# 3. DB schema (миграция идемпотентна — безопасно прогонять на existing БД)
|
||||
psql -h <db-host> -U postgres_user -d postgres -f cryptowallet-schema.sql
|
||||
|
||||
# 4. bitok public key в Vault (для kid из JWT header)
|
||||
vault kv put dev-secrets/jwt/kid active=<kid-from-bitok>
|
||||
vault kv put dev-secrets/jwt/kids/<kid-from-bitok> \
|
||||
algorithm=RS256 \
|
||||
public_key="$(cat /path/to/bitok-public.pem)"
|
||||
```
|
||||
|
||||
⚠️ **Master-key менять нельзя** — все existing encrypted_mnemonic станут нерасшифровываемыми. Сервис логирует WARN если в Vault ключ изменился.
|
||||
⚠️ **Master-key менять нельзя** — все existing `encrypted_mnemonic` станут нерасшифровываемыми. API на старте делает self-test: пытается декриптить любой существующий mnemonic и фейлится если ключ не подошёл.
|
||||
|
||||
## Deploy
|
||||
|
||||
```bash
|
||||
scp -P 2222 -r deployserver/ server@176.124.213.102:~/cryptowallet/
|
||||
ssh server@176.124.213.102 -p 2222
|
||||
cd ~/cryptowallet && cp .env.example .env && nano .env && ./start.sh
|
||||
# Залить bundle на сервер
|
||||
scp -P 2222 -r deployserver/ server@<host>:~/cryptowallet/
|
||||
|
||||
# На сервере: заполнить .env, поднять
|
||||
ssh server@<host> -p 2222
|
||||
cd ~/cryptowallet
|
||||
cp .env.example .env
|
||||
chmod 600 .env
|
||||
nano .env # заполни VAULT_*, JWT_*, CORS_ORIGINS
|
||||
./start.sh
|
||||
```
|
||||
|
||||
В `.env` обязательны: `VAULT_ADDR`, `VAULT_ROLE_ID`, `VAULT_SECRET_ID`, `JWT_ISSUER`, `JWT_AUDIENCE`, `CORS_ORIGINS`.
|
||||
|
||||
## Endpoints (24)
|
||||
|
||||
| Method | Path | Описание |
|
||||
|---|---|---|
|
||||
| GET | /api/health | Liveness (public) |
|
||||
| GET | /api/docs | Swagger UI |
|
||||
| POST | **/api/wallets/create** | **Сервер создаёт коша** (no body, returns addresses) |
|
||||
| GET | /api/wallets | Список адресов юзера |
|
||||
| POST | **/api/wallets/mnemonic/reveal** | Reveal seed (body confirm + 5/час) |
|
||||
| GET | /api/wallets/{chain}/balance | Баланс |
|
||||
| GET | /api/wallets/{chain}/transactions | История tx |
|
||||
| POST | **/api/wallets/{chain}/send** | **Сервер подписывает + broadcast** |
|
||||
| ... | /api/btc/* /api/tron/* /api/sol/* /api/bsc/* /api/relay/* | Proxy endpoints |
|
||||
|
||||
## Security highlights
|
||||
|
||||
- **AES-256-GCM** для encrypted_mnemonic (12-byte random IV, 16-byte auth tag, fail-secure)
|
||||
- **Master-key set-once** (rotation запрещена)
|
||||
- **Race-safe createWallet**: `db.transaction` + `UPDATE WHERE encrypted_mnemonic IS NULL`
|
||||
- **TRX MITM defense**: local recompute txID + verify raw_data перед подписью
|
||||
- **EVM gas cap** 500 gwei (применён к tx, не только check)
|
||||
- **Address checksum validation** (BTC bitcoinjs-lib, TRX bs58check, SOL PublicKey, EVM EIP-55)
|
||||
- **assertAddressMatch** — derived(mnemonic, path) === DB.address перед подписью
|
||||
- **SOL confirmTransaction** — ждём подтверждения
|
||||
- **BTC** P2WPKH bech32, fee fallback 15 sat/vB + 1.1x safety, dust 294, broadcast 20s timeout
|
||||
- **POST mnemonic/reveal** + CSRF + body confirm token + 5/час + audit-log
|
||||
- **Logger sanitization**: password/token/mnemonic/hex64/BIP39-phrase patterns
|
||||
- **Audit log** `logs/audit.log` (wallet.create / wallet.send / mnemonic.reveal)
|
||||
- **Hourly key rotation**: JWT keys + CSRF secret из Vault (master-key НЕ ротируется)
|
||||
- **Fail-fast**: сервис не стартует без master-key, JWT_ISSUER, JWT_AUDIENCE
|
||||
В `.env` **обязательны**: `VAULT_ADDR`, `VAULT_ROLE_ID`, `VAULT_SECRET_ID`, `JWT_ISSUER=bitok`, `JWT_AUDIENCE`, `CORS_ORIGINS`.
|
||||
|
||||
## Update / Rebuild
|
||||
|
||||
```bash
|
||||
scp -P 2222 -r deployserver/apps server@176.124.213.102:~/cryptowallet/
|
||||
ssh server@176.124.213.102 -p 2222 'cd cryptowallet && docker compose up -d --build'
|
||||
scp -P 2222 -r deployserver/apps server@<host>:~/cryptowallet/
|
||||
ssh server@<host> -p 2222 'cd cryptowallet && docker compose up -d --build'
|
||||
```
|
||||
|
||||
## Endpoints
|
||||
|
||||
| Method | Path | Описание |
|
||||
|---|---|---|
|
||||
| GET | `/api/health` | Liveness (public) |
|
||||
| GET | `/api/docs` | Swagger UI |
|
||||
| GET | `/api/docs/swagger.json` | OpenAPI JSON |
|
||||
| POST | **`/api/wallets/create`** | Сервер создаёт коша (no body, returns 5 addresses) |
|
||||
| GET | `/api/wallets` | Список адресов юзера |
|
||||
| POST | **`/api/wallets/mnemonic/reveal`** | Reveal seed (body confirm + 5/час rate-limit) |
|
||||
| GET | `/api/wallets/{chain}/balance` | Баланс (native + все известные токены чейна) |
|
||||
| GET | `/api/wallets/{chain}/transactions` | История tx |
|
||||
| POST | **`/api/wallets/{chain}/send`** | Сервер подписывает + broadcast. Body: `{to, amount, token?, feeTier?}` |
|
||||
| GET | **`/api/wallets/{chain}/gas-suggestions`** | Slow/normal/fast tiers (ETH/BSC, parsed из eth_feeHistory) |
|
||||
| POST | **`/api/wallets/{chain}/sign-raw-evm-tx`** | Подписать произвольную EVM tx (для Relay/Swap execute responses) |
|
||||
| — | `/api/btc/*` `/api/tron/*` `/api/sol/*` `/api/bsc/*` `/api/relay/*` | Proxy endpoints (swap quote/build, bridge quote/execute/status) |
|
||||
|
||||
## Security highlights
|
||||
|
||||
- **AES-256-GCM** для encrypted_mnemonic (12-byte random IV, 16-byte auth tag, fail-secure)
|
||||
- **Master-key set-once** (rotation запрещена в коде)
|
||||
- **Crypto self-test на старте** — fail-fast если master-key не декриптит existing mnemonics
|
||||
- **Race-safe createWallet** — `db.transaction` + `UPDATE WHERE encrypted_mnemonic IS NULL` (set-once primitive)
|
||||
- **Atomic erc20 update** — ETH-адрес кладётся в `users.erc20` внутри той же транзакции
|
||||
- **TRX MITM defense** — local recompute txID + 4-layer raw_data verification перед подписью
|
||||
- **EVM gas cap** 500 gwei (применён к tx, не только check)
|
||||
- **EVM gas oracle** через `eth_feeHistory` p25/p50/p75 — minimum-but-works fees (BSC floor 0.05, ETH 0.5 gwei)
|
||||
- **BTC fee** tier-based (slow=144 blocks, normal=6, fast=1) + floor 2 sat/vB
|
||||
- **TRX fee_limit** cap 30 TRX (раньше 100, излишне)
|
||||
- **Address checksum validation** (BTC bitcoinjs-lib, TRX bs58check, SOL PublicKey, EVM EIP-55)
|
||||
- **assertAddressMatch** — derived(mnemonic, path) === DB.address перед подписью
|
||||
- **SOL confirmTransaction** — ждём подтверждения сети
|
||||
- **BTC** P2WPKH bech32, dust 294, broadcast 20s timeout
|
||||
- **POST mnemonic/reveal** + CSRF + body confirm token + 5/час rate-limit + audit-log
|
||||
- **Logger sanitization** — password/token/mnemonic/hex64/BIP39-phrase patterns маскируются
|
||||
- **Audit log в stdout** (структурированный JSON с `"level":"audit"`) — `wallet.create` / `wallet.send` / `mnemonic.reveal` / `wallet.sign_raw_evm`
|
||||
- **Hourly key rotation** — JWT keys + CSRF secret из Vault (master-key НЕ ротируется)
|
||||
- **Fail-fast** — сервис не стартует без master-key, JWT_ISSUER, JWT_AUDIENCE
|
||||
- **Container hardening** — read-only fs, cap_drop ALL, no-new-privileges, pids/mem/cpu limits, non-root uid 1001, loopback-only port
|
||||
- **Relay proxy** whitelist method+path — `/quote` (POST), `/intents/status/v3` (GET), `/execute/{swap|bridge}` (POST). Никаких freeform action'ов
|
||||
- **Bridge UI dropdown** ETH/BSC/SOL only (TRX через Relay убран — плохая ликвидность)
|
||||
|
||||
## Logs
|
||||
|
||||
Файловых логов **нет**. Всё в stdout, подбирается Docker log driver:
|
||||
|
||||
```bash
|
||||
docker compose logs -f api # все логи (structured JSON)
|
||||
docker compose logs api | grep '"level":"audit"' # только audit events
|
||||
docker compose logs api | grep '"level":"ERROR"' # только ошибки
|
||||
```
|
||||
|
||||
## Production hardening checklist (опционально)
|
||||
|
||||
- [ ] Vault server-mode (raft/file backend) с unseal flow
|
||||
- [ ] TLS termination на reverse-proxy (Caddy / Nginx) перед `127.0.0.1:3001`
|
||||
- [ ] Swagger UI скрыть за basic-auth (endpoints всё ещё доступны через `/api/docs/swagger.json`)
|
||||
- [ ] Postgres backups (pg_dump → S3 по cron)
|
||||
- [ ] Vault root token ротация
|
||||
- [ ] Mnemonic-reveal endpoint — 2FA / time-based confirmation tokens
|
||||
- [ ] Rate-limit tune под реальный трафик
|
||||
|
||||
Reference in New Issue
Block a user