robust_client_socket 0.5.2 → 0.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.md CHANGED
@@ -1,7 +1,67 @@
1
1
  # RobustClientSocket
2
2
 
3
+ ⚠️ Not Production Tested (yet)
4
+
5
+ `Not vibecoded`
6
+
3
7
  HTTP-клиент для защищённых межсервисных коммуникаций с автоматической генерацией токенов авторизации.
4
8
 
9
+ ## ПОЧЕМУ (WHY)
10
+
11
+ ### Проблема
12
+
13
+ При межсервисном взаимодействии в микросервисной архитектуре возникают следующие проблемы:
14
+
15
+ - **Отсутствие аутентификации**: HTTP-запросы между сервисами не защищены
16
+ - **Replay-атаки**: Перехваченные запросы могут быть повторно использованы
17
+ - **Ручное управление ключами**: Сложность работы с RSA-ключами для каждого сервиса
18
+ - **Boilerplate код**: Повторяющийся код для шифрования и отправки запросов
19
+
20
+ ### Решение
21
+
22
+ RobustClientSocket предоставляет:
23
+
24
+ - **Автоматическую токенизацию**: Каждый запрос получает уникальный зашифрованный токен
25
+ - **RSA-шифрование**: Асимметричное шифрование с валидацией ключей
26
+ - **Keychain управление**: Автогенерация HTTP-клиентов для каждого сервиса
27
+ - **Защиту от replay-атак**: Timestamp в каждом токене
28
+
29
+ ## КАК ЭТО РАБОТАЕТ (HOW)
30
+
31
+ ### Архитектура
32
+
33
+ ```
34
+ ┌───────────────────┐ ┌───────────────────┐
35
+ │ Your Service │ │ Target Service │
36
+ │ │ │ (RobustServer) │
37
+ └─────────┬─────────┘ └─────────┬─────────┘
38
+ │ │
39
+ v v
40
+ ┌───────────────────┐ ┌───────────────────┐
41
+ │ RobustClientSocket│ │ RobustServerSocket│
42
+ │ │ │ │
43
+ │ 1. Generate token │────>│ 1. Decrypt token │
44
+ │ 2. RSA encrypt │ │ 2. Validate │
45
+ │ 3. Send request │ │ 3. Check limits │
46
+ └───────────────────┘ └───────────────────┘
47
+ ```
48
+
49
+ ### Поток запроса
50
+
51
+ 1. **Формирование токена**: `{client_name}_{timestamp}`
52
+ 2. **RSA-шифрование**: Токен шифруется публичным ключом целевого сервиса
53
+ 3. **Base64 кодирование**: Для передачи в HTTP-заголовке
54
+ 4. **Отправка**: Запрос в заголовке
55
+ 5. **Валидация**: Сервер расшифровывает и проверяет токен
56
+
57
+ ### Структура токена
58
+
59
+ ```
60
+ Base64(RSA_Encrypt("{service_name}_{unix_timestamp}"))
61
+
62
+ Пример: Base64(RSA_Encrypt("core_1704067200"))
63
+ ```
64
+
5
65
  ## 📋 Содержание
6
66
 
7
67
  - [Функции безопасности](#функции-безопасности)
@@ -11,8 +71,6 @@ HTTP-клиент для защищённых межсервисных комм
11
71
  - [Методы HTTP](#методы-http)
12
72
  - [Обработка ошибок](#обработка-ошибок)
13
73
  - [SSL/TLS настройки](#ssltls-настройки)
14
- - [Рекомендации по использованию](#рекомендации-по-использованию)
15
- - [Безопасность в production](#безопасность-в-production)
16
74
 
17
75
  ## 🔒 Функции безопасности
18
76
 
@@ -134,7 +192,7 @@ ECDHE-ECDSA-AES256-GCM-SHA384
134
192
 
135
193
  ### Автоматическая генерация клиентов
136
194
 
137
- После `RobustClientSocket.load!` автоматически создаются классы для каждого сервиса в keychain:
195
+ После `RobustClientSocket.load!` автоматически создаются классы для каждого объявленного сервиса:
138
196
 
139
197
  ```ruby
140
198
  # Конфигурация
@@ -219,40 +277,9 @@ response = RobustClientSocket::Payments.get(
219
277
  # Secure-Token заголовок добавляется автоматически!
220
278
  ```
221
279
 
222
- ### Обработка ответов
223
-
224
- ```ruby
225
- response = RobustClientSocket::Payments.get('/api/v1/balance')
226
-
227
- # Проверка успешности
228
- if response.success?
229
- # 2xx статус коды
230
- data = response.parsed_response
231
- puts "Balance: #{data['amount']}"
232
- end
233
-
234
- # Доступ к деталям
235
- response.code # HTTP статус код (Integer)
236
- response.message # HTTP статус сообщение (String)
237
- response.body # Сырое тело ответа (String)
238
- response.parsed_response # Распарсенный JSON (Hash)
239
- response.headers # Заголовки ответа (Hash)
240
-
241
- # Проверка конкретных статусов
242
- if response.code == 200
243
- # OK
244
- elsif response.code == 404
245
- # Not Found
246
- elsif response.code == 401
247
- # Unauthorized - возможно истёк токен
248
- elsif response.code == 429
249
- # Rate limit exceeded
250
- end
251
- ```
252
-
253
280
  ## 🌐 Методы HTTP
254
281
 
255
- Все стандартные HTTP методы поддерживаются:
282
+ Все стандартные HTTPpartry методы:
256
283
 
257
284
  ### GET
258
285
  ```ruby
@@ -293,7 +320,7 @@ RobustClientSocket::ServiceName.options(path, options = {})
293
320
 
294
321
  ```ruby
295
322
  {
296
- body: '{"key": "value"}', # Тело запроса (String или Hash)
323
+ body: { "key": "value" }, # Тело запроса (Hash)
297
324
  query: { param: 'value' }, # Query параметры (Hash)
298
325
  headers: { 'X-Custom': 'value' }, # Дополнительные заголовки (Hash)
299
326
  timeout: 30, # Переопределить таймаут запроса
@@ -357,37 +384,6 @@ rescue StandardError => e
357
384
  end
358
385
  ```
359
386
 
360
- ### Retry стратегия
361
-
362
- ```ruby
363
- def call_with_retry(max_attempts: 3, backoff: 2)
364
- attempts = 0
365
-
366
- begin
367
- attempts += 1
368
- response = RobustClientSocket::Payments.get('/api/v1/status')
369
-
370
- return response if response.success?
371
-
372
- # Retry на 5xx и таймаутах
373
- if response.code >= 500 && attempts < max_attempts
374
- sleep(backoff ** attempts)
375
- retry
376
- end
377
-
378
- response
379
-
380
- rescue Timeout::Error, SocketError => e
381
- if attempts < max_attempts
382
- sleep(backoff ** attempts)
383
- retry
384
- else
385
- raise
386
- end
387
- end
388
- end
389
- ```
390
-
391
387
  ## 🔐 SSL/TLS настройки
392
388
 
393
389
  ### Production конфигурация
@@ -412,150 +408,7 @@ RobustClientSocket.configure do |c|
412
408
  end
413
409
  ```
414
410
 
415
- ### Рекомендуемые cipher suites
416
-
417
- **High Security (рекомендуется):**
418
- ```ruby
419
- ciphers: %w[
420
- ECDHE-RSA-AES128-GCM-SHA256
421
- ECDHE-RSA-AES256-GCM-SHA384
422
- ECDHE-ECDSA-AES128-GCM-SHA256
423
- ECDHE-ECDSA-AES256-GCM-SHA384
424
- ]
425
- ```
426
-
427
- **Balanced (совместимость + безопасность):**
428
- ```ruby
429
- ciphers: %w[
430
- ECDHE-RSA-AES128-GCM-SHA256
431
- ECDHE-RSA-AES256-GCM-SHA384
432
- AES128-GCM-SHA256
433
- AES256-GCM-SHA384
434
- ]
435
- ```
436
-
437
- ### Проверка SSL в разных окружениях
438
-
439
- ```ruby
440
- c.payments = {
441
- base_uri: ENV['PAYMENTS_URL'],
442
- public_key: ENV['PAYMENTS_PUBLIC_KEY'],
443
- # Включать SSL только в production
444
- ssl_verify: Rails.env.production?
445
- }
446
- ```
447
-
448
- ## 💡 Рекомендации по использованию
449
-
450
- ### 1. Управление ключами
451
-
452
- **✅ DO:**
453
- ```ruby
454
- # Храните ключи в переменных окружения
455
- c.payments = {
456
- base_uri: ENV['PAYMENTS_URL'],
457
- public_key: ENV['PAYMENTS_PUBLIC_KEY']
458
- }
459
-
460
- # Используйте secrets management
461
- c.payments = {
462
- base_uri: 'https://payments.example.com',
463
- public_key: Rails.application.credentials.dig(:payments, :public_key)
464
- }
465
-
466
- # Один файл для всех публичных ключей
467
- # config/public_keys/payments.pem
468
- c.payments = {
469
- base_uri: ENV['PAYMENTS_URL'],
470
- public_key: File.read(Rails.root.join('config/public_keys/payments.pem'))
471
- }
472
- ```
473
-
474
- **❌ DON'T:**
475
- ```ruby
476
- # НЕ коммитьте ключи в git
477
- c.payments = {
478
- public_key: "-----BEGIN PUBLIC KEY-----\nMII..."
479
- }
480
-
481
- # НЕ используйте один ключ для всех сервисов
482
- ```
483
-
484
- ### 2. Настройка таймаутов
485
-
486
- **Рекомендации по таймаутам:**
487
-
488
- ```ruby
489
- # Быстрые операции (чтение)
490
- c.cache_service = {
491
- base_uri: 'https://cache.example.com',
492
- public_key: ENV['CACHE_PUBLIC_KEY'],
493
- timeout: 3, # 3 секунды
494
- open_timeout: 1 # 1 секунда
495
- }
496
-
497
- # Стандартные операции
498
- c.api_service = {
499
- base_uri: 'https://api.example.com',
500
- public_key: ENV['API_PUBLIC_KEY'],
501
- timeout: 10, # 10 секунд (default)
502
- open_timeout: 5 # 5 секунд (default)
503
- }
504
-
505
- # Долгие операции (обработка, экспорт)
506
- c.processor = {
507
- base_uri: 'https://processor.example.com',
508
- public_key: ENV['PROCESSOR_PUBLIC_KEY'],
509
- timeout: 60, # 60 секунд
510
- open_timeout: 10 # 10 секунд
511
- }
512
- ```
513
-
514
- ### 3. Логирование и мониторинг
515
-
516
- ```ruby
517
- # Wrapper для логирования всех запросов
518
- module RobustClientSocketLogger
519
- def self.call(service_name, method, path, options = {})
520
- start_time = Time.now
521
-
522
- response = RobustClientSocket.const_get(service_name).send(method, path, options)
523
-
524
- duration = ((Time.now - start_time) * 1000).round(2)
525
-
526
- Rails.logger.info(
527
- "RobustClientSocket Request: " \
528
- "service=#{service_name} method=#{method} path=#{path} " \
529
- "status=#{response.code} duration=#{duration}ms"
530
- )
531
-
532
- # Метрики
533
- Metrics.timing("robust_client.#{service_name}.#{method}", duration)
534
- Metrics.increment("robust_client.#{service_name}.status.#{response.code}")
535
-
536
- response
537
- rescue StandardError => e
538
- Rails.logger.error(
539
- "RobustClientSocket Error: " \
540
- "service=#{service_name} method=#{method} path=#{path} " \
541
- "error=#{e.class} message=#{e.message}"
542
- )
543
-
544
- Metrics.increment("robust_client.#{service_name}.error.#{e.class.name}")
545
- raise
546
- end
547
- end
548
-
549
- # Использование
550
- response = RobustClientSocketLogger.call(
551
- 'Payments',
552
- :post,
553
- '/api/v1/charge',
554
- body: { amount: 1000 }.to_json
555
- )
556
- ```
557
-
558
- ### 4. Синхронизация с сервером
411
+ ### 2. Синхронизация с сервером
559
412
 
560
413
  ```ruby
561
414
  # Клиент
@@ -575,101 +428,6 @@ RobustServerSocket.configure do |c|
575
428
  end
576
429
  ```
577
430
 
578
- ### 5. Тестирование
579
-
580
- ```ruby
581
- # spec/support/robust_client_socket.rb
582
- RSpec.configure do |config|
583
- config.before(:suite) do
584
- RobustClientSocket.configure do |c|
585
- c.client_name = 'test_service'
586
-
587
- c.payments = {
588
- base_uri: 'http://localhost:3001',
589
- public_key: File.read(Rails.root.join('spec/fixtures/keys/payments_public.pem'))
590
- }
591
- end
592
-
593
- RobustClientSocket.load!
594
- end
595
- end
596
-
597
- # Тест с WebMock
598
- require 'webmock/rspec'
599
-
600
- RSpec.describe 'Payments integration' do
601
- before do
602
- stub_request(:post, "http://localhost:3001/api/v1/charge")
603
- .to_return(status: 200, body: { success: true }.to_json)
604
- end
605
-
606
- it 'creates charge' do
607
- response = RobustClientSocket::Payments.post(
608
- '/api/v1/charge',
609
- body: { amount: 1000 }.to_json
610
- )
611
-
612
- expect(response.success?).to be true
613
- expect(response.parsed_response['success']).to be true
614
- end
615
- end
616
- ```
617
-
618
- ## 🔐 Безопасность в Production
619
-
620
- ### Чеклист безопасности
621
-
622
- - [ ] **Публичные ключи хранятся в secrets manager**
623
- - [ ] **Использованы RSA-2048 или выше ключи**
624
- - [ ] **ssl_verify: true для production**
625
- - [ ] **HTTPS используется для всех сервисов**
626
- - [ ] **client_name синхронизирован с allowed_services серверов**
627
- - [ ] **Настроены разумные таймауты**
628
- - [ ] **Безопасные cipher suites настроены**
629
- - [ ] **Логирование всех запросов включено**
630
- - [ ] **Метрики собираются**
631
- - [ ] **Retry логика реализована для критичных запросов**
632
-
633
- ### Мониторинг
634
-
635
- ```ruby
636
- # Метрики для отслеживания
637
- # - Количество запросов по сервисам
638
- # - Время ответа (percentiles: p50, p95, p99)
639
- # - Количество ошибок по типам
640
- # - Количество таймаутов
641
- # - Количество retry попыток
642
-
643
- class RobustClientMetrics
644
- def self.track(service, method, path)
645
- start = Time.now
646
- response = yield
647
- duration = ((Time.now - start) * 1000).round(2)
648
-
649
- # StatsD/Prometheus metrics
650
- Metrics.timing("robust_client.request.duration", duration, tags: [
651
- "service:#{service}",
652
- "method:#{method}",
653
- "status:#{response.code}"
654
- ])
655
-
656
- Metrics.increment("robust_client.request.count", tags: [
657
- "service:#{service}",
658
- "method:#{method}",
659
- "status:#{response.code}"
660
- ])
661
-
662
- response
663
- rescue StandardError => e
664
- Metrics.increment("robust_client.error.count", tags: [
665
- "service:#{service}",
666
- "error:#{e.class.name}"
667
- ])
668
- raise
669
- end
670
- end
671
- ```
672
-
673
431
  ## 🤝 Интеграция с RobustServerSocket
674
432
 
675
433
  ### Полный пример настройки
@@ -719,6 +477,7 @@ openssl rsa -in service_b_private.pem -pubout -out service_b_public.pem
719
477
 
720
478
  ## 📚 Дополнительные ресурсы
721
479
 
480
+ - [BENCHMARK_ANALYSIS.md](../BENCHMARK_ANALYSIS.md)
722
481
  - [RobustServerSocket documentation](../robust_server_socket/README.ru.md)
723
482
  - [HTTParty documentation](https://github.com/jnunemaker/httparty)
724
483
  - [OpenSSL Ruby documentation](https://ruby-doc.org/stdlib/libdoc/openssl/rdoc/OpenSSL.html)
@@ -729,4 +488,5 @@ openssl rsa -in service_b_private.pem -pubout -out service_b_public.pem
729
488
 
730
489
  ## 🐛 Баги и предложения
731
490
 
732
- Сообщайте о проблемах через issue tracker вашего репозитория.
491
+ Сообщайте о багах через ишью, или напрямую тг @cruel_mango или email tee0zed@gmail.com
492
+
data/img.png ADDED
Binary file
data/img_1.png ADDED
Binary file
@@ -61,7 +61,7 @@ module RobustClientSocket
61
61
  end
62
62
 
63
63
  def time_now_in_utc
64
- Time.now.utc.to_i
64
+ Process.clock_gettime(Process::CLOCK_REALTIME, :millisecond)
65
65
  end
66
66
  end
67
67
  end
data/lib/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RobustClientSocket
4
- VERSION = '0.5.2'
4
+ VERSION = '0.5.3'
5
5
  end
@@ -19,7 +19,7 @@ Gem::Specification.new do |spec|
19
19
  f.start_with?(*%w[features/ .git .circleci Gemfile])
20
20
  end
21
21
  end
22
- spec.require_paths = %w[lib config]
22
+ spec.require_paths = %w[lib]
23
23
  spec.add_dependency 'oj'
24
24
  spec.add_dependency 'httparty'
25
25
  spec.add_dependency 'rspec'
@@ -60,9 +60,9 @@ RSpec.describe RobustClientSocket::HTTP::Helpers do
60
60
  end
61
61
 
62
62
  describe '.time_now_in_utc' do
63
- it 'returns current UTC timestamp' do
64
- allow(Time).to receive_message_chain(:now, :utc, :to_i).and_return(1234567890)
65
- expect(dummy_class.send(:time_now_in_utc)).to eq(1234567890)
63
+ it 'returns current UTC timestamp in milliseconds' do
64
+ allow(Process).to receive(:clock_gettime).with(Process::CLOCK_REALTIME, :millisecond).and_return(1234567890123)
65
+ expect(dummy_class.send(:time_now_in_utc)).to eq(1234567890123)
66
66
  end
67
67
  end
68
68
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: robust_client_socket
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.2
4
+ version: 0.5.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Taras Zhuk
@@ -73,10 +73,14 @@ extensions: []
73
73
  extra_rdoc_files: []
74
74
  files:
75
75
  - ".rspec"
76
+ - BENCHMARK_ANALYSIS.md
76
77
  - CODE_OF_CONDUCT.md
77
78
  - LICENSE.txt
79
+ - README.en.md
78
80
  - README.md
79
81
  - Rakefile
82
+ - img.png
83
+ - img_1.png
80
84
  - lib/robust_client_socket.rb
81
85
  - lib/robust_client_socket/configuration.rb
82
86
  - lib/robust_client_socket/http/client.rb
@@ -96,7 +100,6 @@ metadata: {}
96
100
  rdoc_options: []
97
101
  require_paths:
98
102
  - lib
99
- - config
100
103
  required_ruby_version: !ruby/object:Gem::Requirement
101
104
  requirements:
102
105
  - - ">="
@@ -108,7 +111,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
108
111
  - !ruby/object:Gem::Version
109
112
  version: '0'
110
113
  requirements: []
111
- rubygems_version: 3.6.9
114
+ rubygems_version: 4.0.6
112
115
  specification_version: 4
113
116
  summary: Robust Client Socket
114
117
  test_files: []