Безопасность
Полное руководство по безопасности системы EFKO Kernel, включая аутентификацию, авторизацию, CSRF защиту и управление ключами.
Обзор
Система использует многоуровневую модель безопасности: - Аутентификация — JWT токены (access + refresh) - Авторизация — ролевая модель на основе ролей - CSRF защита — Double Submit Cookie для браузерных клиентов - Rate limiting — защита от DDoS атак - Валидация — проверка данных на границах системы
Аутентификация
JWT Токены
Система использует JWT (JSON Web Token) для аутентификации:
- Access Token — краткоживущий токен для доступа к API
- Refresh Token — долгоживущий токен для обновления сессии
Access Token
Назначение: Доступ к защищенным эндпоинтам API
Алгоритм: RS256 (RSA с SHA-256) или HS256
TTL: Конфигурируется через JWT_ACCESS_TTL (по умолчанию 15 минут, во время разработки 7 дней, так как CORS_ORIGIN в development mode * и CORS_CREDENTIALS false)
Передача: В заголовке Authorization: Bearer <token>
Структура Payload
{
sub: string; // UUID пользователя
email: string; // Email пользователя
role: UserRole; // Роль пользователя
iat: number; // Issued At (timestamp)
exp: number; // Expiration (timestamp)
iss: string; // Issuer (из JWT_ACCESS_ISSUER)
}
Пример
{
"sub": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"email": "ivan@example.com",
"role": "admin",
"iat": 1705248000,
"exp": 1705253400,
"iss": "efko-kernel"
}
Refresh Token
Назначение: Обновление access токена без повторного логина
TTL: Конфигурируется через JWT_REFRESH_TTL (по умолчанию 7 дней)
Хранение:
- Браузерные клиенты: httpOnly cookie
- Мобильные клиенты: в памяти приложения
- В dev режиме cookie не передается, ручка refresh недоступна
Rotation
Refresh токены ротируются при каждом обновлении сессии: - Старый токен аннулируется (помечается как revoked) - Выдается новый токен - Защищает от replay атак
Хранение в БД
model RefreshToken {
id String @id @default(uuid())
userId String
tokenHash String @db.VarChar(255)
expiresAt DateTime
isRevoked Boolean @default(false)
createdAt DateTime @default(now())
user User @relation(...)
}
Password Hashing
Пароли хешируются с использованием bcrypt:
- Алгоритм: bcrypt
- Salt rounds: Конфигурируется (по умолчанию 10)
- Хранение: В поле passwordHash таблицы users
// Хеирование
const hash = await bcrypt.hash(password, 10);
// Проверка
const isValid = await bcrypt.compare(password, hash);
Авторизация
Ролевая модель
Система использует ролевую модель с 5 предопределенными ролями:
| Роль | Код | Описание | Права |
|---|---|---|---|
| Администратор | ADMIN |
Полный доступ ко всем операциям | Все CRUD операции, управление пользователями |
| Менеджер | MANAGER |
Управление данными в доменах | CRUD в Personnel и Production |
| Менеджер смены | SHIFT_MANAGER |
Операционное управление | Чтение данных, создание смен |
| Аналитик | ANALYST |
Только чтение | Только GET запросы |
| Сотрудник | EMPLOYEE |
Базовый доступ | Только собственные данные |
Реализация
Auth Guard
AuthGuard проверяет JWT токен и добавляет пользователя в запрос:
@UseGuards(AuthGuard)
@Controller('personnel')
export class PersonnelController {
@Get('employees')
@Auth(UserRole.ADMIN, UserRole.MANAGER)
getEmployees() {
// Только ADMIN и MANAGER
}
}
Role Guard
RoleGuard проверяет роль пользователя:
@Auth(UserRole.ADMIN, UserRole.MANAGER)
@Post('departments')
createDepartment() {
// Требуется ADMIN или MANAGER
}
Проверка в Downstream сервисах
Domain сервисы также проверяют роли:
// В domain сервисе
if (!allowedRoles.includes(user.role)) {
throw new ForbiddenException('Insufficient permissions');
}
CSRF Защита
Обзор
Gateway использует паттерн Double Submit Cookie для защиты от CSRF атак браузерных клиентов.
Как это работает
- При логине: сервер устанавливает cookie
XSRF-TOKEN(не httpOnly, читаемый JS) - При запросах: браузер должен читать эту cookie и отправлять её значение в заголовке
X-CSRF-Token - Проверка: сервер сравнивает значение cookie и заголовка
- Результат: несоответствие или отсутствие заголовка → 401 Unauthorized
Mobile клиенты
Мобильные клиенты не используют cookies и автоматически исключаются из CSRF проверок: - Нет cookie механизма → проверка пропускается - Refresh токен передается в теле запроса при обновлении сессии
Конфигурация
| Переменная окружения | Значения | Описание |
|---|---|---|
CSRF_ENABLED |
true / false |
Принудительное включение CSRF |
NODE_ENV |
production / development |
CSRF всегда включен в production |
В development CSRF пропускается, если CSRF_ENABLED=true не установлено.
Cookie настройки
XSRF-TOKEN cookie настраивается в CookieConfigService:
- httpOnly: false — должен быть читаемым JavaScript
- sameSite: 'strict' в production
- Очищается при logout вместе с refreshToken
Endpoints
Все POST, PATCH, DELETE эндпоинты требуют CSRF валидации для браузерных клиентов. Токен генерируется и устанавливается на:
- POST /auth/login
- POST /auth/refresh-session
Интеграция для браузерных клиентов
// Чтение CSRF токена после логина
function getCsrfToken(): string {
return document.cookie
.split('; ')
.find(row => row.startsWith('XSRF-TOKEN='))
?.split('=')[1] ?? '';
}
// Добавление к мутирующим запросам
fetch('/api/personnel/employees', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${accessToken}`,
'X-CSRF-Token': getCsrfToken(),
},
body: JSON.stringify(payload),
});
Csrf Guard
CsrfGuard применяется глобально через APP_GUARD в AppModule:
GET,HEAD,OPTIONS— пропускаются (safe methods)- Нет
XSRF-TOKENcookie — пропускается (mobile клиент) - Cookie есть, заголовок отсутствует — 401 Unauthorized
- Значения не совпадают — 401 Unauthorized
- Значения совпадают — запрос проходит
JWT Key Rotation
Обзор
Регулярная ротация JWT ключей важна для безопасности системы.
Зачем ротировать ключи
- Безопасность: Ограничивает окно экспозиции при компрометации ключа
- Compliance: Многие стандарты безопасности требуют периодической ротации
- Best Practice: Индустриальный стандарт для production систем
Переменные окружения
JWT_ACCESS_SECRET— секрет для подписи access токеновJWT_REFRESH_SECRET— секрет для подписи refresh токеновJWT_ACCESS_ISSUER— идентификатор issuer для валидации JWT
Стратегия ротации
Вариант 1: Graceful Rotation с поддержкой двойных ключей
Позволяет использовать старые и новые ключи во время переходного периода.
Шаги:
-
Генерация новых ключей
-
Обновление конфигурации с поддержкой двойных ключей
-
Развертывание нового ключа на все сервисы одновременно
- Обновить переменные окружения для: gateway, auth-service
-
Перезапустить сервисы
-
Мониторинг 1-2 недели
- Проверять логи на ошибки аутентификации
-
Следить за ошибками валидации JWT
-
Удаление старого ключа
- Обновить переменные окружения
- Перезапустить сервисы
Вариант 2: Немедленная ротация (рекомендуется для некритичных систем)
Проще, но вызывает кратковременное прерывание — существующие токены становятся невалидными.
Шаги:
-
Генерация нового ключа
-
Обновление переменных окружения
-
Перезапуск всех сервисов
-
Уведомление пользователей
- Существующие сессии будут инвалидированы
- Пользователям нужно повторно войти
Чек-лист перед ротацией
- [ ] Генерация новых секретных ключей
- [ ] Бэкап текущих секретов
- [ ] Планирование окна обслуживания (при немедленной ротации)
- [ ] Подготовка плана отката
- [ ] Уведомление стейкхолдеров
- [ ] Тестирование нового ключа в staging окружении
Верификация после ротации
# Тест аутентификации с новым токеном
curl -X POST http://localhost:3000/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"test@example.com","password":"password"}'
# Проверка подписи токена на jwt.io
Процедура отката
Если проблемы после ротации:
- Откат к предыдущему секрету в переменных окружения
- Перезапуск всех сервисов
- Исследование логов для определения причины
- Повторная ротация после исправления проблем
Рекомендуемый график ротации
- Development: каждые 30 дней
- Staging: каждые 60 дней
- Production: каждые 90 дней
Best Practices для хранения ключей
- Никогда не коммитить секреты в git — использовать переменные окружения или secret management
- Использовать сильные секреты — минимум 32 байта (256 бит) для HS256
- Регулярная ротация ключей — следовать графику выше
- Мониторинг несанкционированного доступа — проверять на неожиданные ошибки аутентификации
- Разные секреты для разных окружений — dev, staging, production
Экстренная ротация
Если ключ скомпрометирован:
- Немедленная ротация — использовать Вариант 2 (немедленная ротация)
- Исследование логов — определить когда произошла компрометация
- Уведомление security team — если применимо
- Проверка логов доступа — определить потенциально затронутых пользователей
- Принудительный сброс паролей — если учетные данные пользователей могут быть скомпрометированы
Rate Limiting
Профили лимитов
Система использует три профиля rate limiting:
| Профиль | Лимит | Период | Применение |
|---|---|---|---|
| short | 20 req | 1 s | Глобально |
| medium | 100 req | 10 s | Глобально |
| long | 500 req | 60 s | Глобально |
Auth endpoints
Auth endpoints имеют отдельные строгие лимиты:
- POST /auth/register — 3 req / 60 s
- POST /auth/login — 5 req / 60 s
Ответ при превышении лимита
Конфигурация
@Throttle({ short: { limit: 3, ttl: 60_000 } })
@Post('register')
register() {
// Лимит: 3 запроса за 60 секунд
}
Валидация
ValidationPipe
NestJS ValidationPipe применяется глобально для валидации DTO на границах системы:
app.useGlobalPipes(
new ValidationPipe({
whitelist: true, // Отбрасывать неизвестные поля
forbidNonWhitelisted: true, // Ошибка при неизвестных полях
transform: true, // Автоматическое преобразование типов
}),
);
Пример DTO
export class RegisterUserCommandDTO {
@IsEmail()
email: string;
@IsString()
@MinLength(8)
password: string;
@IsString()
@IsNotEmpty()
fullName: string;
@IsEnum(UserRole)
@IsOptional()
role?: UserRole;
@IsUUID()
@IsOptional()
employeeId?: string;
}
Ошибки валидации
{
"statusCode": 400,
"message": [
"email must be an email",
"password must be longer than or equal to 8 characters"
],
"error": "Bad Request"
}
Безопасность окружения
Переменные окружения
Критичные переменные окружения:
# JWT
JWT_ACCESS_SECRET=<strong-secret>
JWT_REFRESH_SECRET=<strong-secret>
JWT_ACCESS_ISSUER=efko-kernel
JWT_ACCESS_TTL=15m
JWT_REFRESH_TTL=7d
# CSRF
CSRF_ENABLED=true
NODE_ENV=production
# Базы данных
DATABASE_URL=<postgresql-connection-string>
MONGO_URI=<mongodb-connection-string>
AMQP_URI=<rabbitmq-connection-string>
Хранение секретов
Не делайте:
- Коммитить .env файлы в git
- Хранить секреты в коде
- Использовать слабые пароли
Делайте: - Использовать секреты в переменных окружения - Использовать secret management (Vault, AWS Secrets Manager, etc.) - Генерировать сильные случайные секреты - Разные секреты для разных окружений
Логирование безопасности
Аудит событий
Система логирует следующие события безопасности: - Неудачные попытки логина - Попытки доступа без авторизации - Попытки доступа с недостаточными правами - CSRF ошибки - Rate limit превышения - JWT валидация ошибки
Логи
Логи пишутся в формате JSON через Pino:
- logs/gateway.log
- logs/auth-service.log
- logs/personnel.log
- logs/production.log
Пример лога:
{
"level": "warn",
"time": "2025-01-15T10:30:00Z",
"requestId": "req-123",
"userId": "user-456",
"action": "AUTH_LOGIN_FAILED",
"ip": "192.168.1.100",
"message": "Invalid credentials for user ivan@example.com"
}
Best Practices
Для разработчиков
- Всегда используйте HTTPS в production
- Валидируйте все входные данные на границах системы
- Используйте параметризованные запросы для БД
- Не раскрывайте детали ошибок в production
- Логируйте события безопасности
- Регулярно обновляйте зависимости
- Используйте CSP headers для веб-клиентов
Для DevOps
- Разделяйте секреты по окружениям
- Ротируйте ключи регулярно
- Мониторируйте подозрительную активность
- Резервируйте конфигурацию
- Используйте managed сервисы (RDS, etc.)
- Настраивайте firewall правила
- Включите audit logging
Для клиентов
- Храните токены безопасно
- Не храните секреты в коде клиента
- Используйте HTTPS для всех запросов
- Валидируйте SSL сертификаты
- Реализуйте timeout для запросов
- Обрабатывайте ошибки безопасности корректно
- Логируйте события безопасности на клиенте
Мониторинг безопасности
Ключевые метрики
- Количество неудачных попыток логина
- Количество CSRF ошибок
- Количество rate limit превышений
- Количество JWT валидация ошибок
- Количество ошибок авторизации
Алерты
Настраивайте алерты для: - Резкого увеличения неудачных попыток логина - Успешных логинов с необычных IP - Множественных CSRF ошибок от одного IP - Превышения rate limit