robust_server_socket 0.3.1 → 0.3.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 37064c5a03f119d17df76cb2eba1bd14b91d0cb4732f0d4aa29505614131966f
4
- data.tar.gz: b18b44079588faf7a38979174971f928f50eb5c1535f0fc6ade2980ace6ed771
3
+ metadata.gz: 626993a0fda547dc940291eb09015b688a1f761cb5345664488996ed28a1906c
4
+ data.tar.gz: c5bb810563a9e6682c021b6553296477b3e34ecffeeecc2121534b8e397c7377
5
5
  SHA512:
6
- metadata.gz: 91e42034b36683bfb0d7c481e9f9ef0e3acfae64d43214c0631bf797dbcbd94756e57288cf9a23f2cff2fccea307f019f0ffa93bfea5f86df2f4e751b171fd87
7
- data.tar.gz: cd15f31a2c698f54684912f1ba3a1b87e8cca074ae0e0967b3374e861484d8bd45a546df35f42781beb651f4505d439ffbff643c3bb6db662fbe4666a87a7a83
6
+ metadata.gz: 9361de5026c683ce6e4a92aad34b8a3358248f4774bff96eb423148ee5daf2556fe1c16f7bd98658acba11218b1e6eae67f8f3af714970474070aa256132e2e7
7
+ data.tar.gz: a24681663c355f6117040fcaa538b73fa5bf9a4ff43edf6e962c9d8a3727e2d107bc222233efddffefd5fdb76a282c16c56e1c7eb323488168274175e0f29993
data/README.en.md ADDED
@@ -0,0 +1,652 @@
1
+ # RobustServerSocket
2
+
3
+ Gem for inter-service authorization, used in pair with RobustClientSocket
4
+
5
+ ### ⚠️ Not Production Tested (yet)
6
+
7
+ ## 📋 Table of Contents
8
+
9
+ - [Security Features](#security-features)
10
+ - [Installation](#installation)
11
+ - [Configuration](#configuration)
12
+ - [Usage](#usage)
13
+ - [Error Handling](#error-handling)
14
+
15
+ ## 🔒 Security Features
16
+
17
+ RobustServerSocket implements a multi-layered protection system for inter-service communications:
18
+
19
+ ### 1. Cryptographic Protection
20
+ - **RSA-2048 Encryption**: Uses RSA key pairs with minimum 2048-bit length
21
+ - **Key Validation**: Automatic key size verification during configuration
22
+ - **Asymmetric Encryption**: Private key on server, public keys on clients
23
+
24
+ ### 2. Token Reuse Protection
25
+ - **One-time Tokens**: Each token can only be used once
26
+ - **Redis Blacklist**: Used tokens are automatically added to blacklist
27
+ - **Atomic Verification**: Race conditions prevented via Redis Lua scripts
28
+
29
+ ### 3. Time-based Restrictions
30
+ - **Expiration Time**: Configurable token lifetime
31
+ - **Automatic Expiration**: Tokens automatically become invalid after expiration
32
+ - **Replay Attack Protection**: Old tokens cannot be reused
33
+
34
+ ### 4. Access Control
35
+ - **Client Whitelist**: Only authorized services can connect
36
+ - **Name-based Identification**: Each client must be explicitly listed in `allowed_services`
37
+ - **Token Format Validation**: Strict token structure verification
38
+
39
+ ### 5. Rate Limiting (optional)
40
+ - **DDoS Protection**: Limit number of requests from each client
41
+ - **Sliding Window**: Fair distribution of requests over time
42
+ - **Fail-open Strategy**: If Redis is unavailable, requests are allowed (for reliability)
43
+ - **Per-client Limits**: Individual counters for each client
44
+
45
+ ### 6. Injection Protection
46
+ - **Input Validation**: Type, length, and format verification of tokens
47
+ - **Maximum Token Length**: 2048 character limit
48
+ - **Empty Value Checks**: Rejection of empty or malformed tokens
49
+
50
+ ## 📦 Installation
51
+
52
+ Add to Gemfile:
53
+
54
+ ```ruby
55
+ gem 'robust_server_socket'
56
+ ```
57
+
58
+ Then execute:
59
+
60
+ ```bash
61
+ bundle install
62
+ ```
63
+
64
+ ## ⚙️ Configuration
65
+
66
+ Create file `config/initializers/robust_server_socket.rb`:
67
+
68
+ ```ruby
69
+ RobustServerSocket.configure do |c|
70
+ # REQUIRED PARAMETERS
71
+
72
+ # Service private key (RSA-2048 or higher)
73
+ # IMPORTANT: Store in environment variables, DO NOT commit to git!
74
+ c.private_key = ENV['ROBUST_SERVER_PRIVATE_KEY']
75
+
76
+ # Token lifetime in seconds
77
+ # Recommendation: 1-3 seconds for production (time from client request to server)
78
+ c.token_expiration_time = 3
79
+
80
+ # List of allowed services (whitelist)
81
+ # Must match names in client keychain
82
+ c.allowed_services = %w[core payments notifications]
83
+
84
+ # Redis for storing used tokens
85
+ c.redis_url = ENV.fetch('REDIS_URL', 'redis://localhost:6379/0')
86
+ c.redis_pass = ENV['REDIS_PASSWORD'] # can be nil for local development
87
+
88
+ # OPTIONAL PARAMETERS: Rate Limiting
89
+
90
+ # Enable rate limiting (default: false)
91
+ c.rate_limit_enabled = true
92
+
93
+ # Maximum requests per time window (default: 100)
94
+ c.rate_limit_max_requests = 100
95
+
96
+ # Time window size in seconds (default: 60)
97
+ c.rate_limit_window_seconds = 60
98
+ end
99
+
100
+ # Load configuration with validation
101
+ RobustServerSocket.load!
102
+ ```
103
+
104
+ ## 🚀 Usage
105
+
106
+ ### Basic Authorization
107
+
108
+ ```ruby
109
+ # In controller or middleware
110
+ class ApiController < ApplicationController
111
+ before_action :authenticate_service!
112
+
113
+ private
114
+
115
+ def authenticate_service!
116
+ # Header configured in RobustClientSocket (SECURE-TOKEN default)
117
+ token = request.headers['SECURE-TOKEN']&.sub(/^Bearer /, '')
118
+
119
+ @current_service = RobustServerSocket::ClientToken.validate!(token) # bang method (raises errors)
120
+ rescue RobustServerSocket::ClientToken::InvalidToken
121
+ render json: { error: 'Invalid token' }, status: :unauthorized
122
+ rescue RobustServerSocket::ClientToken::UnauthorizedClient
123
+ render json: { error: 'Unauthorized service' }, status: :forbidden
124
+ rescue RobustServerSocket::ClientToken::UsedToken
125
+ render json: { error: 'Token already used' }, status: :unauthorized
126
+ rescue RobustServerSocket::ClientToken::StaleToken
127
+ render json: { error: 'Token expired' }, status: :unauthorized
128
+ rescue RobustServerSocket::RateLimiter::RateLimitExceeded => e
129
+ render json: { error: e.message }, status: :too_many_requests
130
+ end
131
+
132
+ def authenticate_service
133
+ token = request.headers['SECURE-TOKEN']&.sub(/^Bearer /, '')
134
+ @current_service = RobustServerSocket::ClientToken.new(token)
135
+
136
+ if @current_service.valid? # doesn't raise errors
137
+ # Token is valid
138
+ else
139
+ # Token is invalid
140
+ render json: { error: 'Unauthorized' }, status: :unauthorized
141
+ end
142
+ end
143
+ end
144
+ ```
145
+
146
+ ### Advanced Usage
147
+
148
+ ```ruby
149
+ # Create token object
150
+ token_string = request.headers['SECURE-TOKEN']&.sub(/^Bearer /, '')
151
+ client_token = RobustServerSocket::ClientToken.new(token_string)
152
+
153
+ # Check validity (returns true/false)
154
+ if client_token.valid?
155
+ # Get client name
156
+ client_name = client_token.client
157
+ puts "Authorized client: #{client_name}"
158
+ else
159
+ # Token is invalid
160
+ render json: { error: 'Unauthorized' }, status: :unauthorized
161
+ end
162
+
163
+ # Quick validation with exceptions (recommended)
164
+ begin
165
+ service_token = RobustServerSocket::ClientToken.validate!(token_string)
166
+ client_name = service_token.client
167
+ rescue => e
168
+ # Handle specific errors
169
+ end
170
+ ```
171
+
172
+ ### Manual Rate Limiting
173
+
174
+ ```ruby
175
+ # Check current attempt count
176
+ attempts = RobustServerSocket::RateLimiter.current_attempts('core')
177
+ puts "Core service made #{attempts} requests"
178
+
179
+ # Reset counter for specific client
180
+ RobustServerSocket::RateLimiter.reset!('core')
181
+
182
+ # Check with exception on exceeded limit
183
+ begin
184
+ RobustServerSocket::RateLimiter.check!('core')
185
+ rescue RobustServerSocket::RateLimiter::RateLimitExceeded => e
186
+ puts e.message # "Rate limit exceeded for core: 101/100 requests per 60s"
187
+ end
188
+
189
+ # Check without exception (returns false when exceeded)
190
+ if RobustServerSocket::RateLimiter.check('core')
191
+ # Limit not exceeded
192
+ else
193
+ # Limit exceeded
194
+ end
195
+ ```
196
+
197
+ ## 🚦 Rate Limiting (Request Rate Limiting)
198
+
199
+ ### How It Works
200
+
201
+ Rate Limiter protects your service from overload by limiting the number of requests from each client within a time window.
202
+
203
+ **Characteristics:**
204
+ - **Per-client counters**: Separate counter for each service
205
+ - **Sliding window**: Window resets automatically after time expires
206
+ - **Atomicity**: Increment and check are performed atomically (Redis LUA script)
207
+ - **Fail-open**: When Redis is unavailable, requests are allowed (not blocked)
208
+
209
+ ### Limit Configuration
210
+
211
+ ```ruby
212
+ RobustServerSocket.configure do |c|
213
+ # Enable rate limiting
214
+ c.rate_limit_enabled = true
215
+
216
+ # For low-traffic microservices
217
+ c.rate_limit_max_requests = 50
218
+ c.rate_limit_window_seconds = 60
219
+ end
220
+ ```
221
+
222
+ ### Monitoring
223
+
224
+ ```ruby
225
+ # Check current state
226
+ clients = ['core', 'payments', 'notifications']
227
+ clients.each do |client|
228
+ attempts = RobustServerSocket::RateLimiter.current_attempts(client)
229
+ max = RobustServerSocket.configuration.rate_limit_max_requests
230
+ puts "#{client}: #{attempts}/#{max}"
231
+ end
232
+
233
+ # In metrics (Prometheus, StatsD, etc.)
234
+ clients.each do |client|
235
+ attempts = RobustServerSocket::RateLimiter.current_attempts(client)
236
+ Metrics.gauge("rate_limiter.attempts.#{client}", attempts)
237
+ end
238
+ ```
239
+
240
+ ## ❌ Error Handling
241
+
242
+ ### Exception Types
243
+
244
+ | Exception | Reason | HTTP Status | Action |
245
+ |-----------|--------|-------------|--------|
246
+ | `InvalidToken` | Token cannot be decrypted or has invalid format | 401 | Check token and key correctness |
247
+ | `UnauthorizedClient` | Client not in whitelist | 403 | Add client to `allowed_services` |
248
+ | `UsedToken` | Token has already been used | 401 | Client must request new token |
249
+ | `StaleToken` | Token has expired | 401 | Client must request new token |
250
+ | `RateLimitExceeded` | Rate limit exceeded | 429 | Client should wait or retry later |
251
+
252
+ ### Centralized Error Handling
253
+
254
+ ```ruby
255
+ # In ApplicationController
256
+ rescue_from RobustServerSocket::ClientToken::InvalidToken,
257
+ RobustServerSocket::ClientToken::UsedToken,
258
+ RobustServerSocket::ClientToken::StaleToken,
259
+ with: :unauthorized_response
260
+
261
+ rescue_from RobustServerSocket::ClientToken::UnauthorizedClient,
262
+ with: :forbidden_response
263
+
264
+ rescue_from RobustServerSocket::RateLimiter::RateLimitExceeded,
265
+ with: :rate_limit_response
266
+
267
+ private
268
+
269
+ def unauthorized_response(exception)
270
+ render json: {
271
+ error: 'Authentication failed',
272
+ message: exception.message,
273
+ type: exception.class.name
274
+ }, status: :unauthorized
275
+ end
276
+
277
+ def forbidden_response(exception)
278
+ render json: {
279
+ error: 'Access denied',
280
+ message: exception.message,
281
+ type: exception.class.name
282
+ }, status: :forbidden
283
+ end
284
+
285
+ def rate_limit_response(exception)
286
+ render json: {
287
+ error: 'Too many requests',
288
+ message: exception.message,
289
+ type: exception.class.name,
290
+ retry_after: RobustServerSocket.configuration.rate_limit_window_seconds
291
+ }, status: :too_many_requests
292
+ end
293
+ ```
294
+
295
+ ## 💡 Usage Recommendations
296
+
297
+ ### 1. Key Management
298
+
299
+ **✅ DO:**
300
+ ```ruby
301
+ # Store keys in environment variables
302
+ c.private_key = ENV['ROBUST_SERVER_PRIVATE_KEY']
303
+
304
+ # Use secrets management (AWS Secrets Manager, Vault, etc.)
305
+ c.private_key = Rails.application.credentials.dig(:robust_server, :private_key)
306
+
307
+ # Generate keys correctly
308
+ # openssl genrsa -out private_key.pem 2048
309
+ # openssl rsa -in private_key.pem -pubout -out public_key.pem
310
+ ```
311
+
312
+ **❌ DON'T:**
313
+ ```ruby
314
+ # DON'T commit keys to git
315
+ c.private_key = "-----BEGIN PRIVATE KEY-----\nMII..."
316
+
317
+ # DON'T use weak keys
318
+ # Minimum RSA-2048, RSA-4096 recommended for high security
319
+ ```
320
+
321
+ ### 2. Redis Configuration
322
+
323
+ **✅ DO:**
324
+ ```ruby
325
+ # Use separate namespace for each environment
326
+ c.redis_url = ENV.fetch('REDIS_URL', 'redis://localhost:6379/0')
327
+
328
+ # Configure connection pool in production
329
+ # In config/initializers/redis.rb
330
+ Redis.current = ConnectionPool.new(size: 5, timeout: 5) do
331
+ Redis.new(url: ENV['REDIS_URL'], password: ENV['REDIS_PASSWORD'])
332
+ end
333
+
334
+ # Monitor Redis status
335
+ # Use Redis Sentinel or Cluster for high availability
336
+ ```
337
+
338
+ **❌ DON'T:**
339
+ ```ruby
340
+ # DON'T use same Redis DB for all environments, use separate Redis DB
341
+ # DON'T ignore Redis errors (rate limiter is already fail-open, but log them)
342
+ ```
343
+
344
+ ### 5. Service Whitelist
345
+
346
+ ```ruby
347
+ # Explicitly specify only necessary services
348
+ c.allowed_services = %w[core payments] # ✅
349
+
350
+ # DON'T use wildcards or regular expressions
351
+ c.allowed_services = %w[*] # ❌ DANGEROUS!
352
+
353
+ # Synchronize with client keychain
354
+ # Server (robust_server_socket):
355
+ c.allowed_services = %w[core]
356
+
357
+ # Client (robust_client_socket):
358
+ c.keychain = {
359
+ core: { # ← Must match
360
+ base_uri: 'https://core.example.com',
361
+ public_key: '-----BEGIN PUBLIC KEY-----...'
362
+ }
363
+ }
364
+ ```
365
+
366
+ ## 🤝 Integration with RobustClientSocket
367
+
368
+ For full functionality, configure the client side:
369
+
370
+ ```ruby
371
+ # On client (RobustClientSocket)
372
+ RobustClientSocket.configure do |c|
373
+ c.service_name = 'core' # ← Must be in server's allowed_services
374
+ c.keychain = {
375
+ payments: {
376
+ base_uri: 'https://payments.example.com',
377
+ public_key: '-----BEGIN PUBLIC KEY-----...' # Public key of payments server
378
+ }
379
+ }
380
+ end
381
+
382
+ # On server (RobustServerSocket)
383
+ RobustServerSocket.configure do |c|
384
+ c.allowed_services = %w[core] # ← Matches client's service_name
385
+ c.private_key = '-----BEGIN PRIVATE KEY-----...' # Private pair to public_key
386
+ end
387
+ ```
388
+
389
+ ## 📚 Additional Resources
390
+
391
+ - [RobustClientSocket documentation](https://github.com/tee0zed/robust_client_socket)
392
+ - [RSA encryption best practices](https://www.openssl.org/docs/)
393
+ - [Redis security guide](https://redis.io/topics/security)
394
+
395
+ ## 📝 License
396
+
397
+ See [MIT-LICENSE](MIT-LICENSE) file
398
+
399
+ ## 🐛 Bugs and Suggestions
400
+
401
+ Report issues through your repository's issue tracker.
402
+
403
+
404
+ #### Test: 1000 Requests with Token Validation
405
+
406
+ **Without RobustServerSocket (plain HTTP controller):**
407
+ ```ruby
408
+ Benchmark.measure do
409
+ 1000.times do
410
+ # Regular request without authorization
411
+ get '/api/v1/partners/719a68e4-3457-45dd-8d7f-73f1d367b87a/merchants'
412
+ end
413
+ end
414
+ ```
415
+
416
+ **Results (approximate):**
417
+ - **Real time**: ~2.5 seconds
418
+ - No token verification
419
+ - No RSA decryption
420
+ - No Redis checks
421
+
422
+ ---
423
+
424
+ **With RobustServerSocket (full protection):**
425
+ ```ruby
426
+ Benchmark.measure do
427
+ 1000.times do
428
+ # Request with RobustClientSocket (RSA + tokens)
429
+ RobustClientSocket::CoreApi.get('/api/v1/partners/719a68e4-3457-45dd-8d7f-73f1d367b87a/merchants')
430
+ end
431
+ end
432
+ ```
433
+
434
+ **Results:**
435
+ - **Real time**: 2.77 seconds
436
+ - **User CPU**: 0.23 seconds
437
+ - **System CPU**: 0.54 seconds
438
+ - **Total CPU**: 0.77 seconds
439
+
440
+ ### 📊 Security Overhead Analysis
441
+
442
+ | Operation | Time | % of Request |
443
+ |-----------|------|-------------|
444
+ | **RSA Decryption** | ~0.1-0.2ms | 3-7% |
445
+ | **Redis Token Check** | ~0.05-0.1ms | 2-3% |
446
+ | **Rate Limiting** | ~0.02-0.05ms | 1% |
447
+ | **Whitelist Validation** | <0.01ms | <1% |
448
+ | **Total Overhead** | **~0.2-0.4ms** | **~10-15%** |
449
+
450
+ ### 🎯 Key Findings
451
+
452
+ 1. **Minimal overhead (~0.3ms per request)**
453
+ - RSA-2048 decryption: ~0.15ms
454
+ - Redis operations: ~0.08ms
455
+ - Rate limiting: ~0.03ms
456
+
457
+ 2. **Scales linearly**
458
+ - 100 req/s = +30ms overhead
459
+ - 1000 req/s = +300ms overhead
460
+ - Acceptable for most applications
461
+
462
+ 3. **Redis is main bottleneck**
463
+ - Use Redis Sentinel/Cluster
464
+ - Connection pooling is critical
465
+ - Fail-open strategy for reliability
466
+
467
+ ### 💡 Performance Optimization
468
+
469
+ **1. Redis Connection Pool:**
470
+
471
+ ```ruby
472
+ # config/initializers/redis.rb
473
+ require 'connection_pool'
474
+
475
+ REDIS_POOL = ConnectionPool.new(size: 25, timeout: 5) do
476
+ Redis.new(
477
+ url: ENV['REDIS_URL'],
478
+ password: ENV['REDIS_PASSWORD'],
479
+ reconnect_attempts: 3,
480
+ reconnect_delay: 0.5,
481
+ reconnect_delay_max: 5.0
482
+ )
483
+ end
484
+
485
+ # In RobustServerSocket::SecureToken::Cacher
486
+ def self.with_redis
487
+ REDIS_POOL.with do |redis|
488
+ yield redis
489
+ end
490
+ end
491
+ ```
492
+
493
+ **2. Public Key Caching:**
494
+
495
+ ```ruby
496
+ # Keys are already cached at load!, but can be optimized
497
+ class RobustServerSocket::ClientToken
498
+ # Keys are loaded once at application startup
499
+ # No additional optimization needed
500
+ end
501
+ ```
502
+
503
+ **3. Rate Limiting Optimization:**
504
+
505
+ ```ruby
506
+ RobustServerSocket.configure do |c|
507
+ # For high-load systems
508
+ c.rate_limit_enabled = true
509
+ c.rate_limit_max_requests = 1000 # Increase limit
510
+ c.rate_limit_window_seconds = 60
511
+
512
+ # For low-load systems
513
+ c.rate_limit_max_requests = 100
514
+ c.rate_limit_window_seconds = 60
515
+ end
516
+ ```
517
+
518
+ **4. Token Expiration Optimization:**
519
+
520
+ ```ruby
521
+ # Short lifetime = more requests for new tokens
522
+ c.token_expiration_time = 10.minutes # ❌ High traffic
523
+
524
+ # Optimal time for inter-service calls
525
+ c.token_expiration_time = 3 # ✅ 3 seconds is enough
526
+ ```
527
+
528
+ ### 🔬 Performance Monitoring
529
+
530
+ **Metrics to Track:**
531
+
532
+ ```ruby
533
+ class ApiController < ApplicationController
534
+ around_action :track_auth_performance
535
+
536
+ private
537
+
538
+ def track_auth_performance
539
+ start = Time.now
540
+
541
+ begin
542
+ yield
543
+ ensure
544
+ duration = ((Time.now - start) * 1000).round(2)
545
+
546
+ # Total request time
547
+ Metrics.timing('request.duration', duration, tags: [
548
+ "controller:#{controller_name}",
549
+ "action:#{action_name}"
550
+ ])
551
+
552
+ # Authentication attempts
553
+ if @current_service
554
+ Metrics.increment('auth.success', tags: ["service:#{@current_service.client}"])
555
+ else
556
+ Metrics.increment('auth.failure')
557
+ end
558
+ end
559
+ end
560
+ end
561
+
562
+ # Specific metrics for RobustServerSocket
563
+ module RobustServerSocket
564
+ class ClientToken
565
+ def self.validate_with_metrics!(token)
566
+ start = Time.now
567
+ result = validate!(token)
568
+ duration = ((Time.now - start) * 1000).round(2)
569
+
570
+ Metrics.timing('robust_server.validation.duration', duration)
571
+ result
572
+ rescue StandardError => e
573
+ Metrics.increment('robust_server.validation.error', tags: ["error:#{e.class.name}"])
574
+ raise
575
+ end
576
+ end
577
+ end
578
+ ```
579
+
580
+ ### 📈 Performance at Different Loads
581
+
582
+ | Req/s | Without Protection | With RobustServerSocket | Overhead | Acceptable |
583
+ |-------|-------------------|------------------------|----------|-----------|
584
+ | 10 | 100ms | 103ms | 3ms | ✅ Excellent |
585
+ | 100 | 1s | 1.03s | 30ms | ✅ Excellent |
586
+ | 500 | 5s | 5.15s | 150ms | ✅ Good |
587
+ | 1,000 | 10s | 10.3s | 300ms | ✅ Acceptable |
588
+ | 5,000 | 50s | 51.5s | 1.5s | ⚠️ Redis scaling needed |
589
+ | 10,000 | 100s | 103s | 3s | ⚠️ Need Redis Cluster |
590
+
591
+ **Conclusion:** Up to 1000 req/s - excellent performance. Higher loads require Redis scaling.
592
+
593
+ ### 🚀 Production Recommendations
594
+
595
+ **For high-load systems (>1000 req/s):**
596
+
597
+ 1. **Redis Cluster** - distributed load
598
+ 2. **Connection Pool** - minimum 25-50 connections
599
+ 3. **Monitor Redis** - latency, memory, connections
600
+ 4. **Fail-over Strategy** - Redis Sentinel
601
+ 5. **CDN for static** - reduce overall load
602
+
603
+ **For medium load (100-1000 req/s):**
604
+
605
+ 1. **Standalone Redis** with persistence
606
+ 2. **Connection Pool** - 10-25 connections
607
+ 3. **Basic monitoring**
608
+ 4. **Rate limiting** - spike protection
609
+
610
+ **For low load (<100 req/s):**
611
+
612
+ 1. **Simple Redis** setup
613
+ 2. **Default connection pool** (5)
614
+ 3. **Standard configuration**
615
+
616
+ ## 🤝 Integration with RobustClientSocket
617
+
618
+ For full functionality, configure the client side:
619
+
620
+ ```ruby
621
+ # On client (RobustClientSocket)
622
+ RobustClientSocket.configure do |c|
623
+ c.service_name = 'core' # ← Must be in server's allowed_services
624
+ c.keychain = {
625
+ payments: {
626
+ base_uri: 'https://payments.example.com',
627
+ public_key: '-----BEGIN PUBLIC KEY-----...' # Public key of payments server
628
+ }
629
+ }
630
+ end
631
+
632
+ # On server (RobustServerSocket)
633
+ RobustServerSocket.configure do |c|
634
+ c.allowed_services = %w[core] # ← Matches client's service_name
635
+ c.private_key = '-----BEGIN PRIVATE KEY-----...' # Private pair to public_key
636
+ end
637
+ ```
638
+
639
+ ## 📚 Additional Resources
640
+
641
+ - [BENCHMARK_ANALYSIS.md](BENCHMARK_ANALYSIS.md)
642
+ - [RobustClientSocket documentation](../robust_client_socket/README.md)
643
+ - [RSA encryption best practices](https://www.openssl.org/docs/)
644
+ - [Redis security guide](https://redis.io/topics/security)
645
+
646
+ ## 📝 License
647
+
648
+ See [MIT-LICENSE](MIT-LICENSE) file
649
+
650
+ ## 🐛 Bugs and Suggestions
651
+
652
+ Report issues through your repository's issue tracker.
data/README.md CHANGED
@@ -1,59 +1,387 @@
1
1
  # RobustServerSocket
2
2
 
3
- Gem for in-service Authorization for using with RobustClientSocket
3
+ Gem для межсервисной авторизации, используется в паре с RobustClientSocket
4
4
 
5
- ## Security
5
+ ### ⚠️ Not Production Tested (yet)
6
6
 
7
- - RSA-2048 key pair is used for authorization.
8
- - Authorized client names are stored in token and config
9
- - Token is staleable
10
- - Token if one-time use only
11
- - Blacklist for tokens in redis
7
+ ## 📋 Содержание
12
8
 
13
- ## Usage
9
+ - [Функции безопасности](#функции-безопасности)
10
+ - [Установка](#установка)
11
+ - [Конфигурация](#конфигурация)
12
+ - [Использование](#использование)
13
+ - [Обработка ошибок](#обработка-ошибок)
14
14
 
15
- 'config/initializers/robust_server_socket.rb'
15
+ ## 🔒 Функции безопасности
16
+
17
+ RobustServerSocket реализует многоуровневую систему защиты для межсервисных коммуникаций:
18
+
19
+ ### 1. Криптографическая защита
20
+ - **RSA-2048 шифрование**: Используется пара ключей RSA с минимальной длиной 2048 бит
21
+ - **Валидация ключей**: Автоматическая проверка размера ключа при конфигурации
22
+ - **Асимметричное шифрование**: Приватный ключ на сервере, публичный — у клиентов
23
+
24
+ ### 2. Защита от повторного использования токенов
25
+ - **Одноразовые токены**: Каждый токен может быть использован только один раз
26
+ - **Blacklist в Redis**: Использованные токены автоматически добавляются в черный список
27
+ - **Атомарная проверка**: Race condition защищена благодаря Redis Lua скриптам
28
+
29
+ ### 3. Временные ограничения
30
+ - **Expiration time**: Настраиваемое время жизни токена
31
+ - **Автоматическое истечение**: Токены автоматически становятся недействительными после истечения времени
32
+ - **Защита от replay attacks**: Старые токены не могут быть использованы повторно
33
+
34
+ ### 4. Контроль доступа
35
+ - **Whitelist клиентов**: Только авторизованные сервисы могут подключаться
36
+ - **Идентификация по имени**: Каждый клиент должен быть явно указан в `allowed_services`
37
+ - **Валидация формата токена**: Строгая проверка структуры токена
38
+
39
+ ### 5. Rate Limiting (опционально)
40
+ - **Защита от DDoS**: Ограничение количества запросов от каждого клиента
41
+ - **Sliding window**: Справедливое распределение запросов во времени
42
+ - **Fail-open стратегия**: Если Redis недоступен, запросы пропускаются (для надёжности)
43
+ - **Per-client лимиты**: Индивидуальные счётчики для каждого клиента
44
+
45
+ ### 6. Защита от инъекций
46
+ - **Валидация входных данных**: Проверка типа, длины и формата токенов
47
+ - **Максимальная длина токена**: Ограничение 2048 символов
48
+ - **Проверка на пустые значения**: Отклонение пустых или некорректных токенов
49
+
50
+ ## 📦 Установка
51
+
52
+ ```ruby
53
+ gem 'robust_server_socket'
54
+ ```
55
+
56
+ ## ⚙️ Конфигурация
57
+
58
+ Создайте файл `config/initializers/robust_server_socket.rb`:
16
59
 
17
60
  ```ruby
18
61
  RobustServerSocket.configure do |c|
19
- c.private_key = '-----PRIVATE KEY-----[...]' # private key of the service, from pair of keys by RobustServerSocket
20
- c.token_expiration_time = 10.minutes # time in seconds for token expiration
21
- c.allowed_services = %w(core) # list of services allowed to use this service, must be same as service name in keychain in RobustClientSocket
22
- # so if we have
23
- # RobustClientSocket.configure do |c|
24
- # c.keychain = {
25
- # core: { <<< service name
26
- # base_uri: 'https://core.payrent.com',
27
- # public_key: '-----BEGIN PUBLIC KEY-----[...]'
28
- # },
29
- # we should add 'core' to allowed_services
30
- c.redis_url = 'redis://localhost:6379' # redis url for storing tokens
31
- c.redis_pass = 'password' # redis password
62
+ # ОБЯЗАТЕЛЬНЫЕ ПАРАМЕТРЫ
32
63
 
33
- # Optional: Rate Limiting (disabled by default)
34
- c.rate_limit_enabled = true # enable rate limiting per client
35
- c.rate_limit_max_requests = 100 # maximum requests per window (default: 100)
36
- c.rate_limit_window_seconds = 60 # time window in seconds (default: 60)
37
- end
64
+ # Приватный ключ сервиса (RSA-2048 или выше)
65
+ c.private_key = ENV['ROBUST_SERVER_PRIVATE_KEY']
66
+ c.token_expiration_time = 3
38
67
 
68
+ # Список разрешённых сервисов (whitelist)
69
+ # Должен совпадать с именами RobustClientSocket клиента
70
+ c.allowed_services = %w[core payments notifications]
71
+
72
+ # Redis для работы replay-attack protection и throttling
73
+ c.redis_url = ENV.fetch('REDIS_URL', 'redis://localhost:6379/0')
74
+ c.redis_pass = ENV['REDIS_PASSWORD']
75
+
76
+ # НЕОБЯЗАТЕЛЬНЫЕ ПАРАМЕТРЫ
77
+ # Включить ограничение частоты запросов (по умолчанию: false)
78
+ c.rate_limit_enabled = true
79
+ # Максимальное количество запросов в окне времени (по умолчанию: 100)
80
+ c.rate_limit_max_requests = 100
81
+ # Размер временного окна в секундах (по умолчанию: 60)
82
+ c.rate_limit_window_seconds = 60
83
+ end
84
+
85
+ # Загрузка конфигурации с валидацией
39
86
  RobustServerSocket.load!
40
87
  ```
41
88
 
42
- and then
89
+ ### Опции конфигурации сервиса
90
+
91
+ | Параметр | Тип | Обязательный | Default | Описание |
92
+ |----------|-----|--------------|---------|----------|
93
+ | `private_key` | String | ✅ | - | Приватный RSA ключ сервиса (RSA-2048 или выше) |
94
+ | `token_expiration_time` | Integer | ✅ | - | Время жизни токена в секундах |
95
+ | `allowed_services` | Array | ✅ | - | Список разрешённых сервисов (whitelist) |
96
+ | `redis_url` | String | ✅ | - | URL для подключения к Redis |
97
+ | `redis_pass` | String | ❌ | nil | Пароль для Redis (если требуется) |
98
+ | `rate_limit_enabled` | Boolean | ❌ | false | Включить ограничение частоты запросов |
99
+ | `rate_limit_max_requests` | Integer | ❌ | 100 | Максимальное количество запросов в окне времени |
100
+ | `rate_limit_window_seconds` | Integer | ❌ | 60 | Размер временного окна в секундах |
101
+
102
+ ## 🚀 Использование
103
+
104
+ ### Базовая авторизация
105
+
106
+ ```ruby
107
+ # В контроллере или middleware
108
+ class ApiController < ApplicationController
109
+ before_action :authenticate_service!
110
+
111
+ private
112
+
113
+ def authenticate_service!
114
+ # Хедер, прописанный в RobustClientSocket (SECURE-TOKEN default)
115
+ token = request.headers['SECURE-TOKEN']&.sub(/^Bearer /, '')
116
+
117
+ @current_service = RobustServerSocket::ClientToken.validate!(token) # bang method (рейзит ошибки)
118
+ rescue RobustServerSocket::ClientToken::InvalidToken
119
+ render json: { error: 'Invalid token' }, status: :unauthorized
120
+ rescue RobustServerSocket::ClientToken::UnauthorizedClient
121
+ render json: { error: 'Unauthorized service' }, status: :forbidden
122
+ rescue RobustServerSocket::ClientToken::UsedToken
123
+ render json: { error: 'Token already used' }, status: :unauthorized
124
+ rescue RobustServerSocket::ClientToken::StaleToken
125
+ render json: { error: 'Token expired' }, status: :unauthorized
126
+ rescue RobustServerSocket::RateLimiter::RateLimitExceeded => e
127
+ render json: { error: e.message }, status: :too_many_requests
128
+ end
129
+
130
+ def authenticate_service
131
+ token = request.headers['SECURE-TOKEN']&.sub(/^Bearer /, '')
132
+ @current_service = RobustServerSocket::ClientToken.valid?(token) # не рейзит
133
+
134
+ if @current_service
135
+ # Токен валиден
136
+ else
137
+ # Токен невалиден
138
+ render json: { error: 'Unauthorized' }, status: :unauthorized
139
+ end
140
+ end
141
+ end
142
+ ```
143
+
144
+ ### Расширенное использование
145
+
146
+ ```ruby
147
+ # Создание объекта токена
148
+ token_string = request.headers['Authorization']&.sub(/^Bearer /, '')
149
+ client_token = RobustServerSocket::ClientToken.new(token_string)
150
+
151
+ # Проверка валидности (возвращает true/false)
152
+ if client_token.valid?
153
+ # Получение имени клиента
154
+ client_name = client_token.client
155
+ puts "Authorized client: #{client_name}"
156
+ else
157
+ # Токен невалиден
158
+ render json: { error: 'Unauthorized' }, status: :unauthorized
159
+ end
160
+
161
+ # Быстрая валидация с исключениями
162
+ begin
163
+ service_token = RobustServerSocket::ClientToken.validate!(token_string)
164
+ client_name = service_token.client
165
+ rescue => e
166
+ # Обработка специфичных ошибок
167
+ end
168
+ ```
169
+
170
+ ### Rate Limiting вручную
171
+
172
+ ```ruby
173
+ # Проверка текущего количества попыток
174
+ attempts = RobustServerSocket::RateLimiter.current_attempts('core')
175
+ puts "Core service made #{attempts} requests"
176
+
177
+ # Сброс счётчика для конкретного клиента
178
+ RobustServerSocket::RateLimiter.reset!('core')
179
+
180
+ # Проверка с исключением при превышении
181
+ begin
182
+ RobustServerSocket::RateLimiter.check!('core')
183
+ rescue RobustServerSocket::RateLimiter::RateLimitExceeded => e
184
+ puts e.message # "Rate limit exceeded for core: 101/100 requests per 60s"
185
+ end
186
+
187
+ # Проверка без исключения (возвращает false при превышении)
188
+ if RobustServerSocket::RateLimiter.check('core')
189
+ # Лимит не превышен
190
+ else
191
+ # Лимит превышен
192
+ end
193
+ ```
194
+
195
+ ## 🚦 Rate Limiting (Ограничение частоты запросов)
196
+
197
+ ### Принцип работы
198
+
199
+ Rate Limiter защищает ваш сервис от перегрузки, ограничивая количество запросов от каждого клиента в определённом временном окне.
200
+
201
+ **Характеристики:**
202
+ - **Per-client counters**: Отдельный счётчик для каждого сервиса
203
+ - **Sliding window**: Окно сбрасывается автоматически после истечения времени
204
+ - **Атомарность**: Инкремент и проверка выполняются атомарно (Redis LUA script)
205
+ - **Fail-open**: При недоступности Redis запросы пропускаются (не блокируются)
206
+
207
+ ### Мониторинг
208
+
209
+ ```ruby
210
+ # Проверка текущего состояния
211
+ clients = ['core', 'payments', 'notifications']
212
+ clients.each do |client|
213
+ attempts = RobustServerSocket::RateLimiter.current_attempts(client)
214
+ max = RobustServerSocket.configuration.rate_limit_max_requests
215
+ puts "#{client}: #{attempts}/#{max}"
216
+ end
217
+
218
+ # В метриках (Prometheus, StatsD и т.д.)
219
+ clients.each do |client|
220
+ attempts = RobustServerSocket::RateLimiter.current_attempts(client)
221
+ Metrics.gauge("rate_limiter.attempts.#{client}", attempts)
222
+ end
223
+ ```
224
+
225
+ ## ❌ Обработка ошибок
226
+
227
+ ### Типы исключений
228
+
229
+ | Исключение | Причина | HTTP статус | Действие |
230
+ |-----------|---------|-------------|----------|
231
+ | `InvalidToken` | Токен не может быть расшифрован или имеет неверный формат | 401 | Проверьте корректность токена и ключей |
232
+ | `UnauthorizedClient` | Клиент не в whitelist | 403 | Добавьте клиента в `allowed_services` |
233
+ | `UsedToken` | Токен уже был использован | 401 | Клиент должен запросить новый токен |
234
+ | `StaleToken` | Токен истёк | 401 | Клиент должен запросить новый токен |
235
+ | `RateLimitExceeded` | Превышен лимит запросов | 429 | Клиент должен подождать или ретраить позже |
236
+
237
+ ### Централизованная обработка
238
+
239
+ ```ruby
240
+ # В ApplicationController
241
+ rescue_from RobustServerSocket::ClientToken::InvalidToken,
242
+ RobustServerSocket::ClientToken::UsedToken,
243
+ RobustServerSocket::ClientToken::StaleToken,
244
+ with: :unauthorized_response
245
+
246
+ rescue_from RobustServerSocket::ClientToken::UnauthorizedClient,
247
+ with: :forbidden_response
248
+
249
+ rescue_from RobustServerSocket::RateLimiter::RateLimitExceeded,
250
+ with: :rate_limit_response
251
+
252
+ private
253
+
254
+ def unauthorized_response(exception)
255
+ render json: {
256
+ error: 'Authentication failed',
257
+ message: exception.message,
258
+ type: exception.class.name
259
+ }, status: :unauthorized
260
+ end
261
+
262
+ def forbidden_response(exception)
263
+ render json: {
264
+ error: 'Access denied',
265
+ message: exception.message,
266
+ type: exception.class.name
267
+ }, status: :forbidden
268
+ end
269
+
270
+ def rate_limit_response(exception)
271
+ render json: {
272
+ error: 'Too many requests',
273
+ message: exception.message,
274
+ type: exception.class.name,
275
+ retry_after: RobustServerSocket.configuration.rate_limit_window_seconds
276
+ }, status: :too_many_requests
277
+ end
278
+ ```
279
+
280
+ ## 💡 Рекомендации по использованию
281
+
282
+ ### 1. Управление ключами
283
+
284
+ **✅ DO:**
285
+ ```ruby
286
+ # Храните ключи в переменных окружения
287
+ c.private_key = ENV['ROBUST_SERVER_PRIVATE_KEY']
288
+
289
+ # Используйте secrets management (AWS Secrets Manager, Vault, и т.д.)
290
+ c.private_key = Rails.application.credentials.dig(:robust_server, :private_key)
291
+
292
+ # Генерируйте ключи правильно
293
+ # openssl genrsa -out private_key.pem 2048
294
+ # openssl rsa -in private_key.pem -pubout -out public_key.pem
295
+ ```
296
+
297
+ **❌ DON'T:**
298
+ ```ruby
299
+ # НЕ коммитьте ключи в git
300
+ c.private_key = "-----BEGIN PRIVATE KEY-----\nMII..."
301
+
302
+ # НЕ используйте слабые ключи
303
+ # Минимум RSA-2048, рекомендуется RSA-4096 для высокой безопасности
304
+ ```
305
+
306
+ ### 2. Конфигурация Redis
43
307
 
308
+ **✅ DO:**
44
309
  ```ruby
45
- token = RobustServerSocket::ClientToken.new(token) # token - is a Bearer from secure-token header
46
- token.valid? #Boolean check if token is not expired and client is allowed to use this service, main authorization check
47
- token.client #String name of the client
310
+ # Используйте отдельный namespace для каждого окружения
311
+ c.redis_url = ENV.fetch('REDIS_URL', 'redis://localhost:6379/0')
312
+
313
+ # Настройте connection pool в production
314
+ # В config/initializers/redis.rb
315
+ Redis.current = ConnectionPool.new(size: 5, timeout: 5) do
316
+ Redis.new(url: ENV['REDIS_URL'], password: ENV['REDIS_PASSWORD'])
317
+ end
318
+
319
+ # Мониторьте состояние Redis
320
+ # Используйте Redis Sentinel или Cluster для высокой доступности
321
+ ```
48
322
 
49
- RobustServerSocket::ClientToken.validate!(token) # shortcut for token.valid? and raises specific errors
323
+ **❌ DON'T:**
324
+ ```ruby
325
+ # НЕ используйте одну БД Redis для всех окружений, используйте отдельную bd redis
326
+ # НЕ игнорируйте ошибки Redis (rate limiter уже fail-open, но логируйте их)
50
327
  ```
51
- ## Errors
52
328
 
53
- `RobustServerSocket::ClientToken::UnauthorizedClient` - client is not allowed to use this service you should add it to allowed_services
54
- `RobustServerSocket::ClientToken::UsedToken` - token is already used
55
- `RobustServerSocket::ClientToken::StaleToken` - token is stale over the expiration time
56
- `RobustServerSocket::ClientToken::InvalidToken` - token decryption failed
57
- `RobustServerSocket::ClientToken::RateLimitExceeded` - client exceeded rate limit (only when rate limiting is enabled)
329
+ ### 5. Whitelist сервисов
330
+
331
+ ```ruby
332
+ # Явно указывайте только необходимые сервисы
333
+ c.allowed_services = %w[core payments] #
334
+
335
+ # НЕ используйте wildcards или регулярные выражения
336
+ c.allowed_services = %w[*] # ❌ ОПАСНО!
337
+
338
+ # Синхронизируйте с keychain клиента
339
+ # Server (robust_server_socket):
340
+ c.allowed_services = %w[core]
341
+
342
+ # Client (robust_client_socket):
343
+ c.keychain = {
344
+ core: { # ← Должно совпадать
345
+ base_uri: 'https://core.example.com',
346
+ public_key: '-----BEGIN PUBLIC KEY-----...'
347
+ }
348
+ }
349
+ ```
350
+
351
+ ## 🤝 Интеграция с RobustClientSocket
352
+
353
+ Для полноценной работы необходимо настроить клиентскую часть:
354
+
355
+ ```ruby
356
+ # На клиенте (RobustClientSocket)
357
+ RobustClientSocket.configure do |c|
358
+ c.service_name = 'core' # ← Должно быть в allowed_services сервера
359
+ c.keychain = {
360
+ payments: {
361
+ base_uri: 'https://payments.example.com',
362
+ public_key: '-----BEGIN PUBLIC KEY-----...' # Публичный ключ сервера payments
363
+ }
364
+ }
365
+ end
366
+
367
+ # На сервере (RobustServerSocket)
368
+ RobustServerSocket.configure do |c|
369
+ c.allowed_services = %w[core] # ← Соответствует service_name клиента
370
+ c.private_key = '-----BEGIN PRIVATE KEY-----...' # Приватная пара к public_key
371
+ end
372
+ ```
373
+
374
+ ## 📚 Дополнительные ресурсы
375
+
376
+ - [BENCHMARK_ANALYSIS.md](BENCHMARK_ANALYSIS.md)
377
+ - [RobustClientSocket documentation](https://github.com/tee0zed/robust_client_socket)
378
+ - [RSA encryption best practices](https://www.openssl.org/docs/)
379
+ - [Redis security guide](https://redis.io/topics/security)
380
+
381
+ ## 📝 Лицензия
382
+
383
+ См. файл [MIT-LICENSE](MIT-LICENSE)
58
384
 
385
+ ## 🐛 Баги и предложения
59
386
 
387
+ Сообщайте о проблемах через issue tracker вашего репозитория.
@@ -13,23 +13,7 @@ module RobustServerSocket
13
13
 
14
14
  def self.validate!(secure_token)
15
15
  new(secure_token).tap do |instance|
16
- raise InvalidToken unless instance.decrypted_token
17
- raise UnauthorizedClient unless instance.client
18
-
19
- RateLimiter.check!(instance.client)
20
-
21
- result = instance.atomic_validate_and_log_token
22
-
23
- case result
24
- when 'stale'
25
- raise StaleToken
26
- when 'used'
27
- raise UsedToken
28
- when 'ok'
29
- true
30
- else
31
- raise InvalidToken, "Unexpected validation result: #{result}"
32
- end
16
+ instance.validate!
33
17
  end
34
18
  end
35
19
 
@@ -38,6 +22,26 @@ module RobustServerSocket
38
22
  @client = nil
39
23
  end
40
24
 
25
+ def validate!
26
+ raise InvalidToken unless decrypted_token
27
+ raise UnauthorizedClient unless client
28
+
29
+ RateLimiter.check!(client)
30
+
31
+ result = atomic_validate_and_log_token
32
+
33
+ case result
34
+ when 'stale'
35
+ raise StaleToken
36
+ when 'used'
37
+ raise UsedToken
38
+ when 'ok'
39
+ true
40
+ else
41
+ raise InvalidToken, "Unexpected validation result: #{result}"
42
+ end
43
+ end
44
+
41
45
  def valid?
42
46
  !!(decrypted_token &&
43
47
  client &&
@@ -17,6 +17,5 @@ module RobustServerSocket
17
17
 
18
18
  require_relative 'robust_server_socket/rate_limiter'
19
19
  require_relative 'robust_server_socket/client_token'
20
- require_relative 'robust_server_socket/private_message'
21
20
  end
22
21
  end
data/lib/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RobustServerSocket
4
- VERSION = '0.3.1'
4
+ VERSION = '0.3.3'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: robust_server_socket
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.3.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - tee_zed
@@ -75,6 +75,7 @@ files:
75
75
  - ".rspec"
76
76
  - CODE_OF_CONDUCT.md
77
77
  - LICENSE.txt
78
+ - README.en.md
78
79
  - README.md
79
80
  - Rakefile
80
81
  - lib/robust_server_socket.rb