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.en.md ADDED
@@ -0,0 +1,491 @@
1
+ # RobustClientSocket
2
+
3
+ ⚠️ Not Production Tested (yet)
4
+
5
+ `Not vibecoded`
6
+
7
+ HTTP client for secure inter-service communications with automatic authorization token generation.
8
+
9
+ ## WHY
10
+
11
+ ### The Problem
12
+
13
+ When building microservice communication, the following challenges arise:
14
+
15
+ - **Lack of authentication**: HTTP requests between services are unprotected
16
+ - **Replay attacks**: Intercepted requests can be reused maliciously
17
+ - **Manual key management**: Complexity of handling RSA keys for each service
18
+ - **Boilerplate code**: Repetitive code for encryption and request sending
19
+
20
+ ### The Solution
21
+
22
+ RobustClientSocket provides:
23
+
24
+ - **Automatic tokenization**: Each request receives a unique encrypted token
25
+ - **RSA encryption**: Asymmetric encryption with key validation
26
+ - **Keychain management**: Auto-generation of HTTP clients for each service
27
+ - **Replay attack protection**: Timestamp in every token
28
+
29
+ ## HOW IT WORKS
30
+
31
+ ### Architecture
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
+ ### Request Flow
50
+
51
+ 1. **Token formation**: `{client_name}_{timestamp}`
52
+ 2. **RSA encryption**: Token is encrypted with target service's public key
53
+ 3. **Base64 encoding**: For HTTP header transmission
54
+ 4. **Request sending**: Request with `Secure-Token` header
55
+ 5. **Validation**: Server decrypts and validates the token
56
+
57
+ ### Token Structure
58
+
59
+ ```
60
+ Base64(RSA_Encrypt("{service_name}_{unix_timestamp}"))
61
+
62
+ Example: Base64(RSA_Encrypt("core_1704067200"))
63
+ ```
64
+
65
+ ## 📋 Table of Contents
66
+
67
+ - [Security Features](#security-features)
68
+ - [Installation](#installation)
69
+ - [Configuration](#configuration)
70
+ - [Usage](#usage)
71
+ - [HTTP Methods](#http-methods)
72
+ - [Error Handling](#error-handling)
73
+ - [SSL/TLS Settings](#ssltls-settings)
74
+
75
+ ## 🔒 Security Features
76
+
77
+ RobustClientSocket provides secure communication between microservices:
78
+
79
+ ### 1. Automatic Tokenization
80
+ - **On-the-fly Token Generation**: Each request automatically gets a new token
81
+ - **Timestamps**: Tokens contain UTC timestamp for replay attack protection
82
+ - **One-time Tokens**: Each request uses a unique token
83
+
84
+ ### 2. RSA Encryption
85
+ - **RSA-2048 Minimum**: Automatic key size validation
86
+ - **PKCS1_OAEP_PADDING**: Secure padding for attack protection
87
+ - **Key Validation**: Public key correctness verification during configuration
88
+
89
+ ### 3. TLS/SSL Protection
90
+ - **TLS 1.2+**: Modern encryption protocols
91
+ - **Certificate Verification**: VERIFY_PEER mode for production
92
+ - **Configurable Cipher Suites**: ECDHE support for forward secrecy
93
+ - **HTTPS Enforcement**: Mandatory HTTPS usage in production
94
+
95
+ ### 4. Header Protection
96
+ - **Custom Headers**: Configurable header name for token
97
+ - **User-Agent Identification**: Client versioning
98
+ - **Content-Type Control**: Automatic headers for JSON
99
+
100
+ ### 5. Timeout Attack Protection
101
+ - **Connection Timeout**: Configurable connection timeout (default: 5s)
102
+ - **Request Timeout**: Configurable request timeout (default: 10s)
103
+ - **Hang Protection**: Automatic interruption of long requests
104
+
105
+ ### 6. Multi-service Architecture
106
+ - **Keychain Management**: Separate keys for each service
107
+ - **Automatic Client Generation**: Dynamic class creation for services
108
+ - **Configuration Isolation**: Independent settings for each service
109
+
110
+ ## 📦 Installation
111
+
112
+ Add to Gemfile:
113
+
114
+ ```ruby
115
+ gem 'robust_client_socket'
116
+ ```
117
+
118
+ Then execute:
119
+
120
+ ```bash
121
+ bundle install
122
+ ```
123
+
124
+ ## ⚙️ Configuration
125
+
126
+ Create file `config/initializers/robust_client_socket.rb`:
127
+
128
+ ```ruby
129
+ RobustClientSocket.configure do |c|
130
+ # REQUIRED: Current service (client) name
131
+ # Must match allowed_services on server
132
+ c.client_name = 'core'
133
+
134
+ # OPTIONAL: Header name for token (default: 'Secure-Token')
135
+ c.header_name = 'Secure-Token'
136
+
137
+ # KEYCHAIN: Configuration for each target service
138
+
139
+ # Basic configuration (without SSL validation)
140
+ c.payments = {
141
+ base_uri: 'http://localhost:3001',
142
+ public_key: ENV['PAYMENTS_PUBLIC_KEY']
143
+ }
144
+
145
+ # Production configuration with SSL
146
+ c.notifications = {
147
+ base_uri: 'https://notifications.example.com',
148
+ public_key: ENV['NOTIFICATIONS_PUBLIC_KEY'],
149
+ ssl_verify: true, # Enable SSL certificate verification
150
+ timeout: 15, # Request timeout (seconds)
151
+ open_timeout: 5 # Connection timeout (seconds)
152
+ }
153
+
154
+ # Configuration with custom cipher suites
155
+ c.core = {
156
+ base_uri: 'https://core.example.com',
157
+ public_key: ENV['CORE_PUBLIC_KEY'],
158
+ ssl_verify: true,
159
+ ciphers: %w[
160
+ ECDHE-RSA-AES128-GCM-SHA256
161
+ ECDHE-RSA-AES256-GCM-SHA384
162
+ ECDHE-ECDSA-AES128-GCM-SHA256
163
+ ECDHE-ECDSA-AES256-GCM-SHA384
164
+ ]
165
+ }
166
+ end
167
+
168
+ # Load configuration and create clients
169
+ RobustClientSocket.load!
170
+ ```
171
+
172
+ ### Service Configuration Options
173
+
174
+ | Parameter | Type | Required | Default | Description |
175
+ |----------|-----|--------------|---------|----------|
176
+ | `base_uri` | String | ✅ | - | Target service URL |
177
+ | `public_key` | String | ✅ | - | Service RSA public key |
178
+ | `ssl_verify` | Boolean | ❌ | false | Enable SSL certificate verification |
179
+ | `timeout` | Integer | ❌ | 10 | Request timeout (seconds) |
180
+ | `open_timeout` | Integer | ❌ | 5 | Connection timeout (seconds) |
181
+ | `ciphers` | Array/String | ❌ | See below | TLS cipher suites |
182
+
183
+ **Default cipher suites:**
184
+ ```
185
+ ECDHE-RSA-AES128-GCM-SHA256
186
+ ECDHE-RSA-AES256-GCM-SHA384
187
+ ECDHE-ECDSA-AES128-GCM-SHA256
188
+ ECDHE-ECDSA-AES256-GCM-SHA384
189
+ ```
190
+
191
+ ## 🚀 Usage
192
+
193
+ ### Automatic Client Generation
194
+
195
+ After `RobustClientSocket.load!` classes are automatically created for each represented service:
196
+
197
+ ```ruby
198
+ # Configuration
199
+ c.payments = { base_uri: '...', public_key: '...' }
200
+ c.notifications = { base_uri: '...', public_key: '...' }
201
+ c.user_management = { base_uri: '...', public_key: '...' }
202
+
203
+ # After load! classes are available:
204
+ RobustClientSocket::Payments # payments -> Payments
205
+ RobustClientSocket::Notifications # notifications -> Notifications
206
+ RobustClientSocket::UserManagement # user_management -> UserManagement
207
+ ```
208
+
209
+ ### Basic Usage
210
+
211
+ ```ruby
212
+ # GET request
213
+ response = RobustClientSocket::Payments.get('/api/v1/transactions')
214
+
215
+ if response.success?
216
+ transactions = response.parsed_response
217
+ puts "Transactions received: #{transactions.count}"
218
+ else
219
+ puts "Error: #{response.code} - #{response.message}"
220
+ end
221
+
222
+ # POST request with body
223
+ response = RobustClientSocket::Notifications.post(
224
+ '/api/v1/send',
225
+ body: {
226
+ user_id: 123,
227
+ message: 'Hello World',
228
+ type: 'email'
229
+ }.to_json
230
+ )
231
+
232
+ # PUT request
233
+ response = RobustClientSocket::UserManagement.put(
234
+ '/api/v1/users/123',
235
+ body: { name: 'John Doe' }.to_json
236
+ )
237
+
238
+ # DELETE request
239
+ response = RobustClientSocket::Payments.delete('/api/v1/transactions/456')
240
+
241
+ # PATCH request
242
+ response = RobustClientSocket::UserManagement.patch(
243
+ '/api/v1/users/123',
244
+ body: { status: 'active' }.to_json
245
+ )
246
+ ```
247
+
248
+ ### Query Parameters
249
+
250
+ ```ruby
251
+ # GET with query parameters
252
+ response = RobustClientSocket::Payments.get(
253
+ '/api/v1/transactions',
254
+ query: {
255
+ status: 'completed',
256
+ date_from: '2024-01-01',
257
+ limit: 100
258
+ }
259
+ )
260
+
261
+ # Automatically converted to:
262
+ # /api/v1/transactions?status=completed&date_from=2024-01-01&limit=100
263
+ ```
264
+
265
+ ### Custom Headers
266
+
267
+ ```ruby
268
+ # Adding additional headers
269
+ response = RobustClientSocket::Payments.get(
270
+ '/api/v1/transactions',
271
+ headers: {
272
+ 'X-Request-ID' => SecureRandom.uuid,
273
+ 'X-User-ID' => current_user.id.to_s
274
+ }
275
+ )
276
+
277
+ # Secure-Token header is added automatically!
278
+ ```
279
+
280
+ ## 🌐 HTTP Methods
281
+
282
+ All standard HTTPpartry methods:
283
+
284
+ ### GET
285
+ ```ruby
286
+ RobustClientSocket::ServiceName.get(path, options = {})
287
+ ```
288
+
289
+ ### POST
290
+ ```ruby
291
+ RobustClientSocket::ServiceName.post(path, options = {})
292
+ ```
293
+
294
+ ### PUT
295
+ ```ruby
296
+ RobustClientSocket::ServiceName.put(path, options = {})
297
+ ```
298
+
299
+ ### DELETE
300
+ ```ruby
301
+ RobustClientSocket::ServiceName.delete(path, options = {})
302
+ ```
303
+
304
+ ### PATCH
305
+ ```ruby
306
+ RobustClientSocket::ServiceName.patch(path, options = {})
307
+ ```
308
+
309
+ ### HEAD
310
+ ```ruby
311
+ RobustClientSocket::ServiceName.head(path, options = {})
312
+ ```
313
+
314
+ ### OPTIONS
315
+ ```ruby
316
+ RobustClientSocket::ServiceName.options(path, options = {})
317
+ ```
318
+
319
+ ### Request Options
320
+
321
+ ```ruby
322
+ {
323
+ body: '{"key": "value"}', # Request body (String or Hash)
324
+ query: { param: 'value' }, # Query parameters (Hash)
325
+ headers: { 'X-Custom': 'value' }, # Additional headers (Hash)
326
+ timeout: 30, # Override request timeout
327
+ open_timeout: 10 # Override connection timeout
328
+ }
329
+ ```
330
+
331
+ ## ❌ Error Handling
332
+
333
+ ### Exception Types
334
+
335
+ | Exception | Reason | Action |
336
+ |-----------|---------|----------|
337
+ | `InsecureConnectionError` | HTTP used in production with `ssl_verify: true` | Use HTTPS |
338
+ | `InvalidCredentialsError` | Missing `base_uri` or `public_key` | Check configuration |
339
+ | `SecurityError` | Key less than 2048 bits or invalid | Use correct RSA-2048+ key |
340
+ | `OpenSSL::PKey::RSAError` | Encryption error | Check public key format |
341
+ | `Timeout::Error` | Timeout exceeded | Increase timeout or check service |
342
+ | `SocketError` | Service unavailable | Check base_uri and network |
343
+
344
+ ### Error Handling
345
+
346
+ ```ruby
347
+ begin
348
+ response = RobustClientSocket::Payments.post(
349
+ '/api/v1/charge',
350
+ body: { amount: 1000 }.to_json
351
+ )
352
+
353
+ if response.success?
354
+ # Success
355
+ elsif response.code == 401
356
+ # Authorization problem
357
+ Rails.logger.error("Auth failed: token may be rejected by server")
358
+ elsif response.code == 422
359
+ # Validation error
360
+ errors = response.parsed_response['errors']
361
+ elsif response.code >= 500
362
+ # Server error
363
+ Rails.logger.error("Server error: #{response.code}")
364
+ end
365
+
366
+ rescue RobustClientSocket::HTTP::Client::InsecureConnectionError => e
367
+ Rails.logger.error("Insecure connection: #{e.message}")
368
+ # Use HTTPS in production
369
+
370
+ rescue Timeout::Error => e
371
+ Rails.logger.error("Request timeout: #{e.message}")
372
+ # Retry or timeout handling
373
+
374
+ rescue SocketError => e
375
+ Rails.logger.error("Service unavailable: #{e.message}")
376
+ # Service unavailable
377
+
378
+ rescue SecurityError => e
379
+ Rails.logger.error("Security error: #{e.message}")
380
+ # Problem with keys or encryption
381
+
382
+ rescue StandardError => e
383
+ Rails.logger.error("Unexpected error: #{e.class} - #{e.message}")
384
+ end
385
+ ```
386
+
387
+ ## 🔐 SSL/TLS Settings
388
+
389
+ ### Production Configuration
390
+
391
+ ```ruby
392
+ RobustClientSocket.configure do |c|
393
+ c.client_name = 'core'
394
+
395
+ c.payments = {
396
+ base_uri: 'https://payments.example.com',
397
+ public_key: ENV['PAYMENTS_PUBLIC_KEY'],
398
+
399
+ # Enable SSL validation
400
+ ssl_verify: true,
401
+
402
+ # TLS 1.2+ with secure ciphers
403
+ ciphers: %w[
404
+ ECDHE-RSA-AES128-GCM-SHA256
405
+ ECDHE-RSA-AES256-GCM-SHA384
406
+ ]
407
+ }
408
+ end
409
+ ```
410
+
411
+ ### 2. Synchronization with Server
412
+
413
+ ```ruby
414
+ # Client
415
+ RobustClientSocket.configure do |c|
416
+ c.client_name = 'core' # ← Important: current service name
417
+
418
+ c.payments = {
419
+ base_uri: 'https://payments.example.com',
420
+ public_key: '-----BEGIN PUBLIC KEY-----...' # PAYMENTS service public key
421
+ }
422
+ end
423
+
424
+ # Server (payments)
425
+ RobustServerSocket.configure do |c|
426
+ c.allowed_services = %w[core] # ← Must contain 'core'
427
+ c.private_key = '-----BEGIN PRIVATE KEY-----...' # Private pair to public key above
428
+ end
429
+ ```
430
+
431
+ ## 🤝 Integration with RobustServerSocket
432
+
433
+ ### Full Setup Example
434
+
435
+ **Service A (client):**
436
+ ```ruby
437
+ # config/initializers/robust_client_socket.rb
438
+ RobustClientSocket.configure do |c|
439
+ c.client_name = 'service_a'
440
+
441
+ c.service_b = {
442
+ base_uri: ENV['SERVICE_B_URL'],
443
+ public_key: ENV['SERVICE_B_PUBLIC_KEY'],
444
+ ssl_verify: Rails.env.production?
445
+ }
446
+ end
447
+
448
+ RobustClientSocket.load!
449
+ ```
450
+
451
+ **Service B (server):**
452
+ ```ruby
453
+ # config/initializers/robust_server_socket.rb
454
+ RobustServerSocket.configure do |c|
455
+ c.allowed_services = %w[service_a] # Allow service_a
456
+ c.private_key = ENV['SERVICE_B_PRIVATE_KEY']
457
+ c.token_expiration_time = 3
458
+ c.redis_url = ENV['REDIS_URL']
459
+ c.redis_pass = ENV['REDIS_PASSWORD']
460
+ end
461
+
462
+ RobustServerSocket.load!
463
+ ```
464
+
465
+ **Key Pair Generation:**
466
+ ```bash
467
+ # Generate private key (for Service B)
468
+ openssl genrsa -out service_b_private.pem 2048
469
+
470
+ # Generate public key (for Service A)
471
+ openssl rsa -in service_b_private.pem -pubout -out service_b_public.pem
472
+
473
+ # Add to environment variables
474
+ # Service A: SERVICE_B_PUBLIC_KEY=$(cat service_b_public.pem)
475
+ # Service B: SERVICE_B_PRIVATE_KEY=$(cat service_b_private.pem)
476
+ ```
477
+
478
+ ## 📚 Additional Resources
479
+
480
+ - [BENCHMARK_ANALYSIS.md](../BENCHMARK_ANALYSIS.md)
481
+ - [RobustServerSocket documentation](../robust_server_socket/README.ru.md)
482
+ - [HTTParty documentation](https://github.com/jnunemaker/httparty)
483
+ - [OpenSSL Ruby documentation](https://ruby-doc.org/stdlib/libdoc/openssl/rdoc/OpenSSL.html)
484
+
485
+ ## 📝 License
486
+
487
+ See [LICENSE.txt](LICENSE.txt) file
488
+
489
+ ## 🐛 Bugs and Suggestions
490
+
491
+ Report issues to my telegram @cruel_mango or to email tee0zed@gmail.com