From cc81d1cb98f53d452175eae454b4d62df362b3be Mon Sep 17 00:00:00 2001 From: Noloquideus Date: Mon, 4 May 2026 17:03:58 +0300 Subject: [PATCH] chore: update docker --- apps/api/src/services/key-rotation.service.ts | 36 +++++---------- docker-compose.yml | 45 ++----------------- 2 files changed, 15 insertions(+), 66 deletions(-) diff --git a/apps/api/src/services/key-rotation.service.ts b/apps/api/src/services/key-rotation.service.ts index 305e652..bdcb293 100644 --- a/apps/api/src/services/key-rotation.service.ts +++ b/apps/api/src/services/key-rotation.service.ts @@ -1,18 +1,13 @@ -import { env, getVaultToken } from '../config/env'; -import { vaultAppRoleLogin } from '../config/vault'; -import { fetchJwtKeysFromVault, swapKeyMap, getKeyMapSize } from './jwt.service'; -import { fetchCsrfConfig, swapCsrfConfig } from './csrf.service'; -import { logger } from '../lib/logger'; +import{env}from '../config/env'; +import{vaultAppRoleLogin}from '../config/vault'; +import{fetchJwtKeysFromVault,swapKeyMap,getKeyMapSize}from './jwt.service'; +import{fetchCsrfConfig,swapCsrfConfig}from './csrf.service'; +import{logger}from '../lib/logger'; -const DEFAULT_INTERVAL_MS = 60 * 60 * 1000; // 1 hour +const DEFAULT_INTERVAL_MS = 60 * 60 * 1000; let timer: NodeJS.Timeout | null = null; -let currentVaultToken: string | null = null; -/** - * Atomic refresh: pre-fetch JWT keys + CSRF config, swap globals only if BOTH succeed. - * При любой ошибке оставляем старые значения в памяти, сервис продолжает работать. - */ export async function refreshAllKeys(): Promise { const { addr, roleId, secretId, mount, jwtKidPath, jwtKidsPrefix, csrfPath } = env.vault; @@ -21,25 +16,17 @@ export async function refreshAllKeys(): Promise { return; } - // Vault token: используем закэшированный из initEnv, либо логинимся заново - let token = currentVaultToken || getVaultToken(); + const token = await vaultAppRoleLogin(addr, roleId, secretId); if (!token) { - const fresh = await vaultAppRoleLogin(addr, roleId, secretId); - if (!fresh) { - logger.error('Key refresh: Vault AppRole login failed'); - return; - } - token = fresh; - currentVaultToken = fresh; + logger.error('Key refresh: Vault AppRole login failed'); + return; } - // ── Pre-fetch обоих секретов параллельно (НЕ мутируя глобал) ─────────── const jwtPromise = fetchJwtKeysFromVault(addr, token, mount, jwtKidPath, jwtKidsPrefix); const csrfPromise = csrfPath ? fetchCsrfConfig(addr, token, mount, csrfPath) : Promise.resolve(null); const [jwtResult, csrfResult] = await Promise.allSettled([jwtPromise, csrfPromise]); - // ── Атомарность: если хоть один обязательный fetch упал — НИЧЕГО не меняем ── if (jwtResult.status === 'rejected') { logger.error(`Key refresh ABORTED — JWT keys fetch failed: ${jwtResult.reason?.message || jwtResult.reason}`); return; @@ -49,7 +36,6 @@ export async function refreshAllKeys(): Promise { return; } - // ── Atomic swap (синхронные операции, нельзя прервать) ────────────────── swapKeyMap(jwtResult.value); if (csrfResult.status === 'fulfilled' && csrfResult.value) { swapCsrfConfig(csrfResult.value); @@ -61,6 +47,7 @@ export async function refreshAllKeys(): Promise { ); } + export function startKeyRotation(intervalMs: number = DEFAULT_INTERVAL_MS): void { if (timer) return; timer = setInterval(() => { @@ -68,12 +55,11 @@ export function startKeyRotation(intervalMs: number = DEFAULT_INTERVAL_MS): void void refreshAllKeys().catch((err) => logger.error(`Key rotation tick failed: ${err?.message || err}`) ); - // На каждый тик — invalidate Vault token (он мог истечь), будет re-login - currentVaultToken = null; }, intervalMs); logger.info(`Key rotation scheduled (every ${intervalMs}ms)`); } + export function stopKeyRotation(): void { if (timer) { clearInterval(timer); diff --git a/docker-compose.yml b/docker-compose.yml index 150a854..2f93c5d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,5 +1,6 @@ services: api: + image: cryptowallet-api:latest build: context: . dockerfile: Dockerfile @@ -9,51 +10,13 @@ services: environment: API_PORT: '${API_PORT:-3001}' LOG_LEVEL: '${LOG_LEVEL:-INFO}' - DB_HOST: '${DB_HOST:-postgres}' - DB_PORT: '${DB_PORT:-5432}' - DB_USER: '${DB_USER:-cryptowallet}' - DB_PASSWORD: '${DB_PASSWORD:-cryptowallet}' - DB_NAME: '${DB_NAME:-cryptowallet}' - VAULT_ADDR: '${VAULT_ADDR:-}' - VAULT_ROLE_ID: '${VAULT_ROLE_ID:-}' - VAULT_SECRET_ID: '${VAULT_SECRET_ID:-}' + VAULT_ADDR: '${VAULT_ADDR:?VAULT_ADDR is required}' + VAULT_ROLE_ID: '${VAULT_ROLE_ID:?VAULT_ROLE_ID is required}' + VAULT_SECRET_ID: '${VAULT_SECRET_ID:?VAULT_SECRET_ID is required}' VAULT_MOUNT_POINT: '${VAULT_MOUNT_POINT:-dev-secrets}' VAULT_SECRET_PATH: '${VAULT_SECRET_PATH:-database}' VAULT_JWT_KID_PATH: '${VAULT_JWT_KID_PATH:-jwt/kid}' VAULT_JWT_KIDS_PREFIX: '${VAULT_JWT_KIDS_PREFIX:-jwt/kids}' VAULT_CSRF_PATH: '${VAULT_CSRF_PATH:-}' JWT_ALGORITHM: '${JWT_ALGORITHM:-RS256}' - JWT_ISSUER: '${JWT_ISSUER:-auth-service}' - JWT_AUDIENCE: '${JWT_AUDIENCE:-elcsa}' - CORS_ORIGINS: '${CORS_ORIGINS:-}' - CORS_ALLOW_CREDENTIALS: '${CORS_ALLOW_CREDENTIALS:-true}' - RELAY_API_KEY: '${RELAY_API_KEY:-}' - TRON_API_KEY: '${TRON_API_KEY:-}' - JUPITER_API_KEY: '${JUPITER_API_KEY:-}' - JUPITER_REFERRAL_ACCOUNT: '${JUPITER_REFERRAL_ACCOUNT:-}' JUPITER_FEE_BPS: '${JUPITER_FEE_BPS:-70}' - ETHERSCAN_API_KEY: '${ETHERSCAN_API_KEY:-}' - BSCSCAN_API_KEY: '${BSCSCAN_API_KEY:-}' - depends_on: - postgres: - condition: service_healthy - - postgres: - image: postgres:16-alpine - restart: unless-stopped - environment: - POSTGRES_USER: '${DB_USER:-cryptowallet}' - POSTGRES_PASSWORD: '${DB_PASSWORD:-cryptowallet}' - POSTGRES_DB: '${DB_NAME:-cryptowallet}' - ports: - - '${POSTGRES_PORT:-5432}:5432' - volumes: - - postgres-data:/var/lib/postgresql/data - healthcheck: - test: ['CMD-SHELL','pg_isready -U "$$POSTGRES_USER" -d "$$POSTGRES_DB"'] - interval: 5s - timeout: 5s - retries: 10 - -volumes: - postgres-data: