RobustServerSocket
Gem для межсервисной аутентификации, используется в паре с RobustClientSocket
⚠️ Not Production Tested (yet) but tested in staging environment
Not vibecoded
ПОЧЕМУ (WHY)
Проблема
При построении микросервисной архитектуры серверная сторона сталкивается с:
- Отсутствием верификации: Как проверить, что запрос пришёл от доверенного сервиса?
- Replay-атаками: Перехваченные запросы могут быть повторены
- DDoS-атаками: Необходимость ограничения частоты запросов
- Boilerplate кодом: Повторяющаяся логика валидации в каждом сервисе
Решение
RobustServerSocket предоставляет:
- RSA-дешифрование: Проверка подлинности токенов
- Whitelist клиентов: Только разрешённые сервисы
- Защиту от replay: Блэклист использованных токенов в Redis
- Rate limiting: Ограничение запросов на клиента
КАК ЭТО РАБОТАЕТ (HOW)
Архитектура
Входящий запрос с Secure-Token
│
v
┌──────────────────────────────┐
│ RobustServerSocket │
│ │
│ 1. RSA Decrypt │
│ 2. Validate Format │
│ 3. Check Client Whitelist │
│ 4. Check Rate Limit │
│ 5. Check Token Reuse │
│ 6. Check Token Expiration │
└──────────────┬───────────────┘
│
┌────────┼────────┐
v v
✅ Success ❌ Error
(continue) (401/403/429)
Поток валидации
- Расшифровка: Base64 decode → RSA decrypt с приватным ключом
- Парсинг: Извлечение
{client_name}_{timestamp}из токена - Whitelist: Проверка client_name в
allowed_services - Rate limit: Проверка количества запросов в окне
- Replay check: Проверка, что токен не использован (Redis)
- Staleness: Проверка timestamp на актуальность
Модульная система
Проверки подключаются через using_modules:
:client_auth_protection— whitelist клиентов:replay_attack_protection— защита от повторного использования:dos_attack_protection— rate limiting
📋 Содержание
🔒 Функции безопасности
RobustServerSocket реализует многоуровневую систему защиты для межсервисных коммуникаций:
1. Криптографическая защита
- RSA-2048 шифрование: Используется пара ключей RSA с минимальной длиной 2048 бит
- Валидация ключей: Автоматическая проверка размера ключа при конфигурации
2. Контроль доступа
- Whitelist клиентов: Только авторизованные сервисы могут подключаться, при включенном модуле
:client_auth_protection - Идентификация по имени: Каждый клиент должен быть явно указан в
allowed_services
3. Защита от перехвата токенов (replay-attack)
- Защита от replay-attack: использованные токены добавляются в черный список и имеют время жизни, при включенном модуле
:replay_attack_protection - Staleness: Токены автоматически становятся недействительными после истечения времени
- Blacklisting использованных токенов: Redis как хранилище черного списка
- Настраиваемое время жизни токенов в черном списке: по умолчанию 10 минут
- Настраиваемое ttl токена: Должно быть в окно ответа между серверами, по умолчанию 10сек
4. Защита от DoS
- Защита от DDoS: Ограничение количества запросов от каждого клиента, при включенном модуле
:dos_attack_protection - Sliding window: Распределение запросов во времени
- Fail-open стратегия: Если Redis недоступен, запросы пропускаются (для надёжности)
-
5. Защита от SSL stripping MITM attack
- Принудительное HTTPS на сервере: Все запросы должны быть совершены по HTTPS, чтобы защитить токены от перехвата
- Включается на RobustClientSoket, ключём
ssl_verify: true
📦 Установка
gem 'robust_server_socket'
и на клиенте:
gem 'robust_client_socket'
⚙️ Конфигурация
Создайте файл config/initializers/robust_server_socket.rb:
RobustServerSocket.configure do |c|
c.using_modules = %i[
:client_auth_protection
:replay_attack_protection
:dos_attack_protection
]
# Приватный ключ сервиса (RSA-2048 или выше)
c.private_key = ENV['ROBUST_SERVER_PRIVATE_KEY']
c.token_expiration_time = 3
# Список разрешённых сервисов (whitelist)
# Должен совпадать с именами RobustClientSocket клиента
# Для client_auth_protection
c.allowed_services = %w[core payments notifications]
# Redis для работы replay_attack_protection и ddos_attack_protection
c.redis_url = ENV.fetch('REDIS_URL', 'redis://localhost:6379/0')
c.redis_pass = ENV['REDIS_PASSWORD']
# ddos_attack_protection
# Максимальное количество запросов в окне времени (по умолчанию: 100)
c.rate_limit_max_requests = 100
# Размер временного окна в секундах (по умолчанию: 60)
c.rate_limit_window_seconds = 60
end
# Загрузка конфигурации с валидацией
RobustServerSocket.load!
using_modules - это используемые модули, добавление или удаление которых изменит поведение гема.
Опции конфигурации сервиса
| Параметр | Тип | Обязательный | Default | Описание |
|---|---|---|---|---|
private_key |
String | ✅ | - | Приватный RSA ключ сервиса (RSA-2048 или выше) |
token_expiration_time |
Integer | ✅ | 10 | Время жизни токена в секундах |
store_used_token_time |
Integer | ✅ | 600 | Время жизни токена в блеклисте в секундах |
allowed_services |
Array | ❌ | - | Список разрешённых сервисов (whitelist) |
redis_url |
String | ✅ | - | URL для подключения к Redis |
using_modules |
Array | ❌ | [:client_auth_protection, :replay_attack_protection, :dos_attack_protection] | Используемые модули |
redis_pass |
String | ❌ | nil | Пароль для Redis (если требуется) |
rate_limit_max_requests |
Integer | ❌ | 100 | Максимальное количество запросов в окне времени |
rate_limit_window_seconds |
Integer | ❌ | 60 | Размер временного окна в секундах |
🚀 Использование
Базовая авторизация
# В контроллере или middleware
class ApiController < ApplicationController
before_action :authenticate_service!
private
def authenticate_service!
# Хедер, прописанный в RobustClientSocket (SECURE-TOKEN default)
token = request.headers['SECURE-TOKEN']&.sub(/^Bearer /, '')
@current_service = RobustServerSocket::ClientToken.validate!(token) # bang method (рейзит ошибки)
rescue RobustServerSocket::ClientToken::InvalidToken
render json: { error: 'Invalid token' }, status: :unauthorized
rescue RobustServerSocket::ClientToken::UnauthorizedClient
render json: { error: 'Unauthorized service' }, status: :forbidden
rescue RobustServerSocket::ClientToken::UsedToken
render json: { error: 'Token already used' }, status: :unauthorized
rescue RobustServerSocket::ClientToken::StaleToken
render json: { error: 'Token expired' }, status: :unauthorized
rescue RobustServerSocket::RateLimiter::RateLimitExceeded => e
render json: { error: e. }, status: :too_many_requests
end
def authenticate_service
token = request.headers['SECURE-TOKEN']&.sub(/^Bearer /, '')
@current_service = RobustServerSocket::ClientToken.valid?(token) # не рейзит
if @current_service
# Токен валиден
else
# Токен невалиден
render json: { error: 'Unauthorized' }, status: :unauthorized
end
end
end
Расширенное использование
# Создание объекта токена
token_string = request.headers['Authorization']&.sub(/^Bearer /, '')
client_token = RobustServerSocket::ClientToken.new(token_string)
# Проверка валидности (возвращает true/false)
if client_token.valid?
# Получение имени клиента
client_name = client_token.client
puts "Authorized client: #{client_name}"
else
# Токен невалиден
render json: { error: 'Unauthorized' }, status: :unauthorized
end
# Быстрая валидация с исключениями
begin
service_token = RobustServerSocket::ClientToken.validate!(token_string)
client_name = service_token.client
rescue => e
# Обработка специфичных ошибок
end
❌ Обработка ошибок
Типы исключений
| Исключение | Причина | HTTP статус | Действие |
|---|---|---|---|
InvalidToken |
Токен не может быть расшифрован или имеет неверный формат | 401 | Проверьте корректность токена и ключей |
UnauthorizedClient |
Клиент не в whitelist | 403 | Добавьте клиента в allowed_services |
UsedToken |
Токен уже был использован | 401 | Клиент должен запросить новый токен |
StaleToken |
Токен истёк | 401 | Клиент должен запросить новый токен |
RateLimitExceeded |
Превышен лимит запросов | 429 | Клиент должен подождать или ретраить позже |
Централизованная обработка
# В ApplicationController
rescue_from RobustServerSocket::ClientToken::InvalidToken,
RobustServerSocket::ClientToken::UsedToken,
RobustServerSocket::ClientToken::StaleToken,
with: :unauthorized_response
rescue_from RobustServerSocket::ClientToken::UnauthorizedClient,
with: :forbidden_response
rescue_from RobustServerSocket::RateLimiter::RateLimitExceeded,
with: :rate_limit_response
private
def (exception)
render json: {
error: 'Authentication failed',
message: exception.,
type: exception.class.name
}, status: :unauthorized
end
def forbidden_response(exception)
render json: {
error: 'Access denied',
message: exception.,
type: exception.class.name
}, status: :forbidden
end
def rate_limit_response(exception)
render json: {
error: 'Too many requests',
message: exception.,
type: exception.class.name,
retry_after: RobustServerSocket.configuration.rate_limit_window_seconds
}, status: :too_many_requests
end
🤝 Интеграция с RobustClientSocket
Для полноценной работы необходимо настроить клиентскую часть:
# На клиенте (RobustClientSocket)
RobustClientSocket.configure do |c|
c.service_name = 'core' # ← Должно быть в allowed_services сервера
c.keychain = {
payments: {
base_uri: 'https://payments.example.com',
public_key: '-----BEGIN PUBLIC KEY-----...' # Публичный ключ сервера payments
}
}
end
# На сервере (RobustServerSocket)
RobustServerSocket.configure do |c|
c.allowed_services = %w[core] # ← Соответствует service_name клиента
c.private_key = '-----BEGIN PRIVATE KEY-----...' # Приватная пара к public_key
end
📚 Дополнительные ресурсы
📝 Лицензия
См. файл MIT-LICENSE
🐛 Баги и предложения
Сообщайте о багах через ишью, или напрямую тг @cruel_mango или email [email protected]