RobustClientSocket
⚠️ Not Production Tested (yet)
Not vibecoded
HTTP-клиент для защищённых межсервисных коммуникаций с автоматической генерацией токенов авторизации.
ПОЧЕМУ (WHY)
Проблема
При межсервисном взаимодействии в микросервисной архитектуре возникают следующие проблемы:
- Отсутствие аутентификации: HTTP-запросы между сервисами не защищены
- Replay-атаки: Перехваченные запросы могут быть повторно использованы
- Ручное управление ключами: Сложность работы с RSA-ключами для каждого сервиса
- Boilerplate код: Повторяющийся код для шифрования и отправки запросов
Решение
RobustClientSocket предоставляет:
- Автоматическую токенизацию: Каждый запрос получает уникальный зашифрованный токен
- RSA-шифрование: Асимметричное шифрование с валидацией ключей
- Keychain управление: Автогенерация HTTP-клиентов для каждого сервиса
- Защиту от replay-атак: Timestamp в каждом токене
КАК ЭТО РАБОТАЕТ (HOW)
Архитектура
┌───────────────────┐ ┌───────────────────┐
│ Your Service │ │ Target Service │
│ │ │ (RobustServer) │
└─────────┬─────────┘ └─────────┬─────────┘
│ │
v v
┌───────────────────┐ ┌───────────────────┐
│ RobustClientSocket│ │ RobustServerSocket│
│ │ │ │
│ 1. Generate token │────>│ 1. Decrypt token │
│ 2. RSA encrypt │ │ 2. Validate │
│ 3. Send request │ │ 3. Check limits │
└───────────────────┘ └───────────────────┘
Поток запроса
- Формирование токена:
{client_name}_{timestamp} - RSA-шифрование: Токен шифруется публичным ключом целевого сервиса
- Base64 кодирование: Для передачи в HTTP-заголовке
- Отправка: Запрос в заголовке
- Валидация: Сервер расшифровывает и проверяет токен
Структура токена
Base64(RSA_Encrypt("{service_name}_{unix_timestamp}"))
Пример: Base64(RSA_Encrypt("core_1704067200"))
📋 Содержание
- Функции безопасности
- Установка
- Конфигурация
- Использование
- Методы HTTP
- Обработка ошибок
- SSL/TLS настройки
🔒 Функции безопасности
RobustClientSocket обеспечивает защищённую коммуникацию между микросервисами:
1. Автоматическая токенизация
- Генерация токенов на лету: Каждый запрос автоматически получает новый токен
- Временные метки: Токены содержат UTC timestamp для защиты от replay attacks
- Одноразовые токены: Каждый запрос использует уникальный токен
2. RSA шифрование
- RSA-2048 минимум: Автоматическая валидация размера ключа
- PKCS1_OAEP_PADDING: Безопасный padding для защиты от атак
- Валидация ключей: Проверка корректности публичных ключей при конфигурации
3. TLS/SSL защита
- TLS 1.2+: Современные протоколы шифрования
- Проверка сертификатов: VERIFY_PEER режим для production
- Настраиваемые cipher suites: Поддержка ECDHE для forward secrecy
- HTTPS enforcement: Обязательное использование HTTPS в production
4. Защита заголовков
- Кастомные заголовки: Настраиваемое имя заголовка для токена
- User-Agent идентификация: Версионирование клиента
- Content-Type контроль: Автоматические заголовки для JSON
5. Защита от timeout атак
- Connection timeout: Настраиваемый таймаут подключения (default: 5s)
- Request timeout: Настраиваемый таймаут запроса (default: 10s)
- Защита от зависания: Автоматическое прерывание долгих запросов
6. Мультисервисная архитектура
- Keychain управление: Отдельные ключи для каждого сервиса
- Автоматическая генерация клиентов: Динамическое создание классов для сервисов
- Изоляция конфигураций: Независимые настройки для каждого сервиса
📦 Установка
Добавьте в Gemfile:
gem 'robust_client_socket'
Затем выполните:
bundle install
⚙️ Конфигурация
Создайте файл config/initializers/robust_client_socket.rb:
RobustClientSocket.configure do |c|
# ОБЯЗАТЕЛЬНО: Имя текущего сервиса (клиента)
# Должно совпадать с allowed_services на сервере
c.client_name = 'core'
# ОПЦИОНАЛЬНО: Имя заголовка для токена (default: 'Secure-Token')
c.header_name = 'Secure-Token'
# KEYCHAIN: Конфигурация для каждого целевого сервиса
# Базовая конфигурация (без SSL валидации)
c.payments = {
base_uri: 'http://localhost:3001',
public_key: ENV['PAYMENTS_PUBLIC_KEY']
}
# Production конфигурация с SSL
c.notifications = {
base_uri: 'https://notifications.example.com',
public_key: ENV['NOTIFICATIONS_PUBLIC_KEY'],
ssl_verify: true, # Включить проверку SSL сертификатов
timeout: 15, # Таймаут запроса (секунды)
open_timeout: 5 # Таймаут подключения (секунды)
}
# Конфигурация с кастомными cipher suites
c.core = {
base_uri: 'https://core.example.com',
public_key: ENV['CORE_PUBLIC_KEY'],
ssl_verify: true,
ciphers: %w[
ECDHE-RSA-AES128-GCM-SHA256
ECDHE-RSA-AES256-GCM-SHA384
ECDHE-ECDSA-AES128-GCM-SHA256
ECDHE-ECDSA-AES256-GCM-SHA384
]
}
end
# Загрузка конфигурации и создание клиентов
RobustClientSocket.load!
Опции конфигурации сервиса
| Параметр | Тип | Обязательный | Default | Описание |
|---|---|---|---|---|
base_uri |
String | ✅ | - | URL целевого сервиса |
public_key |
String | ✅ | - | Публичный RSA ключ сервиса |
ssl_verify |
Boolean | ❌ | false | Включить проверку SSL сертификатов |
timeout |
Integer | ❌ | 10 | Таймаут запроса (секунды) |
open_timeout |
Integer | ❌ | 5 | Таймаут подключения (секунды) |
ciphers |
Array/String | ❌ | См. ниже | TLS cipher suites |
Default cipher suites:
ECDHE-RSA-AES128-GCM-SHA256
ECDHE-RSA-AES256-GCM-SHA384
ECDHE-ECDSA-AES128-GCM-SHA256
ECDHE-ECDSA-AES256-GCM-SHA384
🚀 Использование
Автоматическая генерация клиентов
После RobustClientSocket.load! автоматически создаются классы для каждого объявленного сервиса:
# Конфигурация
c.payments = { base_uri: '...', public_key: '...' }
c.notifications = { base_uri: '...', public_key: '...' }
c.user_management = { base_uri: '...', public_key: '...' }
# После load! доступны классы:
RobustClientSocket::Payments # payments -> Payments
RobustClientSocket::Notifications # notifications -> Notifications
RobustClientSocket::UserManagement # user_management -> UserManagement
Базовое использование
# GET запрос
response = RobustClientSocket::Payments.get('/api/v1/transactions')
if response.success?
transactions = response.parsed_response
puts "Получено транзакций: #{transactions.count}"
else
puts "Ошибка: #{response.code} - #{response.}"
end
# POST запрос с телом
response = RobustClientSocket::Notifications.post(
'/api/v1/send',
body: {
user_id: 123,
message: 'Hello World',
type: 'email'
}.to_json
)
# PUT запрос
response = RobustClientSocket::UserManagement.put(
'/api/v1/users/123',
body: { name: 'John Doe' }.to_json
)
# DELETE запрос
response = RobustClientSocket::Payments.delete('/api/v1/transactions/456')
# PATCH запрос
response = RobustClientSocket::UserManagement.patch(
'/api/v1/users/123',
body: { status: 'active' }.to_json
)
Query параметры
# GET с query параметрами
response = RobustClientSocket::Payments.get(
'/api/v1/transactions',
query: {
status: 'completed',
date_from: '2024-01-01',
limit: 100
}
)
# Автоматически преобразуется в:
# /api/v1/transactions?status=completed&date_from=2024-01-01&limit=100
Кастомные заголовки
# Добавление дополнительных заголовков
response = RobustClientSocket::Payments.get(
'/api/v1/transactions',
headers: {
'X-Request-ID' => SecureRandom.uuid,
'X-User-ID' => current_user.id.to_s
}
)
# Secure-Token заголовок добавляется автоматически!
🌐 Методы HTTP
Все стандартные HTTPpartry методы:
GET
RobustClientSocket::ServiceName.get(path, = {})
POST
RobustClientSocket::ServiceName.post(path, = {})
PUT
RobustClientSocket::ServiceName.put(path, = {})
DELETE
RobustClientSocket::ServiceName.delete(path, = {})
PATCH
RobustClientSocket::ServiceName.patch(path, = {})
HEAD
RobustClientSocket::ServiceName.head(path, = {})
OPTIONS
RobustClientSocket::ServiceName.(path, = {})
Опции запроса
{
body: { "key": "value" }, # Тело запроса (Hash)
query: { param: 'value' }, # Query параметры (Hash)
headers: { 'X-Custom': 'value' }, # Дополнительные заголовки (Hash)
timeout: 30, # Переопределить таймаут запроса
open_timeout: 10 # Переопределить таймаут подключения
}
❌ Обработка ошибок
Типы исключений
| Исключение | Причина | Действие |
|---|---|---|
InsecureConnectionError |
HTTP используется в production с ssl_verify: true |
Используйте HTTPS |
InvalidCredentialsError |
Отсутствуют base_uri или public_key |
Проверьте конфигурацию |
SecurityError |
Ключ меньше 2048 бит или невалиден | Используйте корректный RSA-2048+ ключ |
OpenSSL::PKey::RSAError |
Ошибка шифрования | Проверьте формат публичного ключа |
Timeout::Error |
Превышен таймаут | Увеличьте timeout или проверьте сервис |
SocketError |
Сервис недоступен | Проверьте base_uri и сеть |
Обработка ошибок
begin
response = RobustClientSocket::Payments.post(
'/api/v1/charge',
body: { amount: 1000 }.to_json
)
if response.success?
# Успешно
elsif response.code == 401
# Проблема авторизации
Rails.logger.error("Auth failed: token may be rejected by server")
elsif response.code == 422
# Validation error
errors = response.parsed_response['errors']
elsif response.code >= 500
# Server error
Rails.logger.error("Server error: #{response.code}")
end
rescue RobustClientSocket::HTTP::Client::InsecureConnectionError => e
Rails.logger.error("Insecure connection: #{e.}")
# Используйте HTTPS в production
rescue Timeout::Error => e
Rails.logger.error("Request timeout: #{e.}")
# Retry или обработка таймаута
rescue SocketError => e
Rails.logger.error("Service unavailable: #{e.}")
# Сервис недоступен
rescue SecurityError => e
Rails.logger.error("Security error: #{e.}")
# Проблема с ключами или шифрованием
rescue StandardError => e
Rails.logger.error("Unexpected error: #{e.class} - #{e.}")
end
🔐 SSL/TLS настройки
Production конфигурация
RobustClientSocket.configure do |c|
c.client_name = 'core'
c.payments = {
base_uri: 'https://payments.example.com',
public_key: ENV['PAYMENTS_PUBLIC_KEY'],
# Включить SSL валидацию
ssl_verify: true,
# TLS 1.2+ с безопасными ciphers
ciphers: %w[
ECDHE-RSA-AES128-GCM-SHA256
ECDHE-RSA-AES256-GCM-SHA384
]
}
end
2. Синхронизация с сервером
# Клиент
RobustClientSocket.configure do |c|
c.client_name = 'core' # ← Важно: имя текущего сервиса
c.payments = {
base_uri: 'https://payments.example.com',
public_key: '-----BEGIN PUBLIC KEY-----...' # Публичный ключ PAYMENTS сервиса
}
end
# Сервер (payments)
RobustServerSocket.configure do |c|
c.allowed_services = %w[core] # ← Должно содержать 'core'
c.private_key = '-----BEGIN PRIVATE KEY-----...' # Приватная пара к публичному ключу выше
end
🤝 Интеграция с RobustServerSocket
Полный пример настройки
Сервис A (client):
# config/initializers/robust_client_socket.rb
RobustClientSocket.configure do |c|
c.client_name = 'service_a'
c.service_b = {
base_uri: ENV['SERVICE_B_URL'],
public_key: ENV['SERVICE_B_PUBLIC_KEY'],
ssl_verify: Rails.env.production?
}
end
RobustClientSocket.load!
Сервис B (server):
# config/initializers/robust_server_socket.rb
RobustServerSocket.configure do |c|
c.allowed_services = %w[service_a] # Разрешить service_a
c.private_key = ENV['SERVICE_B_PRIVATE_KEY']
c.token_expiration_time = 3
c.redis_url = ENV['REDIS_URL']
c.redis_pass = ENV['REDIS_PASSWORD']
end
RobustServerSocket.load!
Генерация пары ключей:
# Генерация приватного ключа (для Service B)
openssl genrsa -out service_b_private.pem 2048
# Генерация публичного ключа (для Service A)
openssl rsa -in service_b_private.pem -pubout -out service_b_public.pem
# Добавить в переменные окружения
# Service A: SERVICE_B_PUBLIC_KEY=$(cat service_b_public.pem)
# Service B: SERVICE_B_PRIVATE_KEY=$(cat service_b_private.pem)
📚 Дополнительные ресурсы
- BENCHMARK_ANALYSIS.md
- RobustServerSocket documentation
- HTTParty documentation
- OpenSSL Ruby documentation
📝 Лицензия
См. файл LICENSE.txt
🐛 Баги и предложения
Сообщайте о багах через ишью, или напрямую тг @cruel_mango или email tee0zed@gmail.com