robust_server_socket 0.4.2 → 0.4.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 +4 -4
- data/.rubocop.yml +9 -0
- data/.ruby-version +1 -0
- data/README.en.md +110 -396
- data/README.md +88 -115
- data/Rakefile +4 -8
- data/lib/robust_server_socket/cacher.rb +45 -35
- data/lib/robust_server_socket/client_token.rb +10 -10
- data/lib/robust_server_socket/configuration.rb +21 -9
- data/lib/robust_server_socket/modules/client_auth_protection.rb +2 -0
- data/lib/robust_server_socket/modules/{dos_attack_protection.rb → rate_limit_protection.rb} +4 -2
- data/lib/robust_server_socket/modules/replay_attack_protection.rb +15 -17
- data/lib/robust_server_socket/rate_limiter.rb +11 -26
- data/lib/robust_server_socket/secure_token/decrypt.rb +5 -7
- data/lib/robust_server_socket.rb +3 -3
- data/lib/version.rb +1 -1
- data/robust_server_socket.gemspec +12 -12
- metadata +6 -4
data/README.en.md
CHANGED
|
@@ -17,6 +17,8 @@ When building microservice architecture, the server side faces:
|
|
|
17
17
|
- **DDoS attacks**: Need to limit request frequency
|
|
18
18
|
- **Boilerplate code**: Repetitive validation logic in every service
|
|
19
19
|
|
|
20
|
+
#### Even if infrastructure is behind a DMZ in a private network, there is still room for SSRF or OpenRedirect attacks
|
|
21
|
+
|
|
20
22
|
### The Solution
|
|
21
23
|
|
|
22
24
|
RobustServerSocket provides:
|
|
@@ -24,7 +26,7 @@ RobustServerSocket provides:
|
|
|
24
26
|
- **RSA decryption**: Token authenticity verification
|
|
25
27
|
- **Client whitelist**: Only authorized services allowed
|
|
26
28
|
- **Replay protection**: Blacklist of used tokens in Redis
|
|
27
|
-
- **Rate limiting**:
|
|
29
|
+
- **Rate limiting**: Sliding window per-client request limits
|
|
28
30
|
|
|
29
31
|
## HOW IT WORKS
|
|
30
32
|
|
|
@@ -54,18 +56,18 @@ Incoming request with Secure-Token
|
|
|
54
56
|
### Validation Flow
|
|
55
57
|
|
|
56
58
|
1. **Decryption**: Base64 decode → RSA decrypt with private key
|
|
57
|
-
2. **Parsing**: Extract `{client_name}_{
|
|
59
|
+
2. **Parsing**: Extract `{client_name}_{timestamp_ms}` from token
|
|
58
60
|
3. **Whitelist**: Verify client_name is in `allowed_services`
|
|
59
|
-
4. **Rate limit**:
|
|
61
|
+
4. **Rate limit**: Sliding window — check request count within `rate_limit_window_seconds`
|
|
60
62
|
5. **Replay check**: Verify token hasn't been used (Redis)
|
|
61
|
-
6. **Staleness**: Verify timestamp is current
|
|
63
|
+
6. **Staleness**: Verify timestamp is current (with ±30s clock skew tolerance)
|
|
62
64
|
|
|
63
65
|
### Modular System
|
|
64
66
|
|
|
65
67
|
Checks are enabled via `using_modules`:
|
|
66
68
|
- `:client_auth_protection` — client whitelist
|
|
67
69
|
- `:replay_attack_protection` — prevent token reuse
|
|
68
|
-
- `:
|
|
70
|
+
- `:rate_limit_protection` — sliding window rate limiting
|
|
69
71
|
|
|
70
72
|
## 📋 Table of Contents
|
|
71
73
|
|
|
@@ -82,28 +84,25 @@ RobustServerSocket implements a multi-layered protection system for inter-servic
|
|
|
82
84
|
### 1. Cryptographic Protection
|
|
83
85
|
- **RSA-2048 Encryption**: Uses RSA key pairs with minimum 2048-bit length
|
|
84
86
|
- **Key Validation**: Automatic key size verification during configuration
|
|
85
|
-
- **Asymmetric Encryption**: Private key on server, public keys on clients
|
|
86
87
|
|
|
87
|
-
### 2.
|
|
88
|
-
- **
|
|
89
|
-
- **
|
|
90
|
-
- **Atomic Verification**: Race conditions prevented via Redis Lua scripts
|
|
88
|
+
### 2. Access Control
|
|
89
|
+
- **Client Whitelist**: Only authorized services can connect, when `:client_auth_protection` module is enabled
|
|
90
|
+
- **Name-based Identification**: Each client must be explicitly listed in `allowed_services`
|
|
91
91
|
|
|
92
|
-
### 3.
|
|
93
|
-
- **
|
|
94
|
-
- **
|
|
95
|
-
- **
|
|
92
|
+
### 3. Replay Attack Protection
|
|
93
|
+
- **One-time Tokens**: Used tokens are added to a Redis blacklist, when `:replay_attack_protection` is enabled
|
|
94
|
+
- **Staleness Check**: Tokens automatically become invalid after `token_expiration_time`
|
|
95
|
+
- **Clock Skew Tolerance**: ±30 seconds (CLOCK_SKEW)
|
|
96
|
+
- **Blacklist TTL**: Computed automatically as `token_expiration_time + CLOCK_SKEW`
|
|
96
97
|
|
|
97
|
-
### 4.
|
|
98
|
-
- **
|
|
99
|
-
- **
|
|
100
|
-
- **
|
|
98
|
+
### 4. Rate Limiting
|
|
99
|
+
- **Sliding Window**: When `:rate_limit_protection` is enabled — precise request counting without the burst effect of fixed windows
|
|
100
|
+
- **Fail-open Strategy**: If Redis is unavailable, requests are allowed through (for service reliability)
|
|
101
|
+
- **Per-client Isolation**: Counter is tracked individually per `client_name`
|
|
101
102
|
|
|
102
|
-
### 5.
|
|
103
|
-
- **
|
|
104
|
-
- **
|
|
105
|
-
- **Fail-open Strategy**: If Redis is unavailable, requests are allowed (for reliability)
|
|
106
|
-
- **Per-client Limits**: Individual counters for each client
|
|
103
|
+
### 5. SSL Stripping / MITM Protection
|
|
104
|
+
- **Enforce HTTPS on server**: All requests should be made over HTTPS to protect tokens from interception
|
|
105
|
+
- **Enabled on RobustClientSocket with `ssl_verify: true`**
|
|
107
106
|
|
|
108
107
|
### 6. Injection Protection
|
|
109
108
|
- **Input Validation**: Type, length, and format verification of tokens
|
|
@@ -112,16 +111,13 @@ RobustServerSocket implements a multi-layered protection system for inter-servic
|
|
|
112
111
|
|
|
113
112
|
## 📦 Installation
|
|
114
113
|
|
|
115
|
-
Add to Gemfile:
|
|
116
|
-
|
|
117
114
|
```ruby
|
|
118
115
|
gem 'robust_server_socket'
|
|
119
116
|
```
|
|
120
117
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
bundle install
|
|
118
|
+
and on the client:
|
|
119
|
+
```ruby
|
|
120
|
+
gem 'robust_client_socket'
|
|
125
121
|
```
|
|
126
122
|
|
|
127
123
|
## ⚙️ Configuration
|
|
@@ -130,125 +126,120 @@ Create file `config/initializers/robust_server_socket.rb`:
|
|
|
130
126
|
|
|
131
127
|
```ruby
|
|
132
128
|
RobustServerSocket.configure do |c|
|
|
133
|
-
|
|
134
|
-
|
|
129
|
+
c.using_modules = %i[
|
|
130
|
+
client_auth_protection
|
|
131
|
+
replay_attack_protection
|
|
132
|
+
rate_limit_protection
|
|
133
|
+
]
|
|
134
|
+
|
|
135
135
|
# Service private key (RSA-2048 or higher)
|
|
136
|
-
# IMPORTANT: Store in environment variables, DO NOT commit to git!
|
|
137
136
|
c.private_key = ENV['ROBUST_SERVER_PRIVATE_KEY']
|
|
138
|
-
|
|
139
|
-
# Token lifetime in seconds
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
137
|
+
|
|
138
|
+
# Token lifetime in seconds (must match TTL on client side)
|
|
139
|
+
c.token_expiration_time = 10
|
|
140
|
+
|
|
143
141
|
# List of allowed services (whitelist)
|
|
144
|
-
# Must match
|
|
142
|
+
# Must match service_name in RobustClientSocket
|
|
145
143
|
c.allowed_services = %w[core payments notifications]
|
|
146
|
-
|
|
147
|
-
# Redis for
|
|
144
|
+
|
|
145
|
+
# Redis for replay_attack_protection and rate_limit_protection
|
|
148
146
|
c.redis_url = ENV.fetch('REDIS_URL', 'redis://localhost:6379/0')
|
|
149
|
-
c.redis_pass = ENV['REDIS_PASSWORD']
|
|
147
|
+
c.redis_pass = ENV['REDIS_PASSWORD']
|
|
150
148
|
|
|
151
|
-
#
|
|
152
|
-
c.rate_limit_max_requests = 100
|
|
153
|
-
|
|
154
|
-
# Time window size in seconds (default: 60)
|
|
155
|
-
c.rate_limit_window_seconds = 60
|
|
149
|
+
# rate_limit_protection
|
|
150
|
+
c.rate_limit_max_requests = 100 # max requests per window (default: 100)
|
|
151
|
+
c.rate_limit_window_seconds = 60 # window size in seconds (default: 60)
|
|
156
152
|
end
|
|
157
153
|
|
|
158
|
-
# Load configuration with validation
|
|
159
154
|
RobustServerSocket.load!
|
|
160
155
|
```
|
|
161
156
|
|
|
157
|
+
### Configuration Options
|
|
158
|
+
|
|
159
|
+
| Parameter | Type | Required | Default | Description |
|
|
160
|
+
|-----------------------------|---------|----------|-------------------------------------------------------------------------------------|-------------------------------------------------|
|
|
161
|
+
| `private_key` | String | ✅ | — | Service private RSA key (RSA-2048 or higher) |
|
|
162
|
+
| `token_expiration_time` | Integer | ✅ | 10 | Token lifetime in seconds |
|
|
163
|
+
| `allowed_services` | Array | ✅ | — | Allowed services whitelist |
|
|
164
|
+
| `redis_url` | String | ✅ | — | Redis connection URL |
|
|
165
|
+
| `redis_pass` | String | ❌ | nil | Redis password |
|
|
166
|
+
| `using_modules` | Array | ❌ | `[:client_auth_protection, :rate_limit_protection, :replay_attack_protection]` | Enabled modules |
|
|
167
|
+
| `rate_limit_max_requests` | Integer | ❌ | 100 | Max requests per window |
|
|
168
|
+
| `rate_limit_window_seconds` | Integer | ❌ | 60 | Window size in seconds |
|
|
169
|
+
|
|
170
|
+
> `store_used_token_time` is no longer configurable — computed automatically as `token_expiration_time + 30` (CLOCK_SKEW).
|
|
171
|
+
|
|
172
|
+
### Compatibility with RobustClientSocket
|
|
173
|
+
|
|
174
|
+
The token contains a timestamp in **milliseconds**. RobustClientSocket starting from version X.X must generate:
|
|
175
|
+
|
|
176
|
+
```ruby
|
|
177
|
+
Process.clock_gettime(Process::CLOCK_REALTIME, :millisecond)
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
The legacy `Time.now.utc.to_i` (seconds) will cause all tokens to be rejected as `stale`.
|
|
181
|
+
|
|
162
182
|
## 🚀 Usage
|
|
163
183
|
|
|
164
184
|
### Basic Authorization
|
|
165
185
|
|
|
166
186
|
```ruby
|
|
167
|
-
# In controller or middleware
|
|
168
187
|
class ApiController < ApplicationController
|
|
169
188
|
before_action :authenticate_service!
|
|
170
|
-
|
|
189
|
+
|
|
171
190
|
private
|
|
172
|
-
|
|
191
|
+
|
|
173
192
|
def authenticate_service!
|
|
174
|
-
# Header configured in RobustClientSocket (SECURE-TOKEN default)
|
|
175
193
|
token = request.headers['SECURE-TOKEN']&.sub(/^Bearer /, '')
|
|
176
|
-
|
|
177
|
-
@current_service = RobustServerSocket::ClientToken.validate!(token) # bang method (raises errors)
|
|
194
|
+
@current_service = RobustServerSocket::ClientToken.validate!(token)
|
|
178
195
|
rescue RobustServerSocket::ClientToken::InvalidToken
|
|
179
196
|
render json: { error: 'Invalid token' }, status: :unauthorized
|
|
180
|
-
rescue RobustServerSocket::
|
|
197
|
+
rescue RobustServerSocket::Modules::ClientAuthProtection::UnauthorizedClient
|
|
181
198
|
render json: { error: 'Unauthorized service' }, status: :forbidden
|
|
182
|
-
rescue RobustServerSocket::
|
|
199
|
+
rescue RobustServerSocket::Modules::ReplayAttackProtection::UsedToken
|
|
183
200
|
render json: { error: 'Token already used' }, status: :unauthorized
|
|
184
|
-
rescue RobustServerSocket::
|
|
201
|
+
rescue RobustServerSocket::Modules::ReplayAttackProtection::StaleToken
|
|
185
202
|
render json: { error: 'Token expired' }, status: :unauthorized
|
|
186
203
|
rescue RobustServerSocket::RateLimiter::RateLimitExceeded => e
|
|
187
204
|
render json: { error: e.message }, status: :too_many_requests
|
|
188
205
|
end
|
|
189
|
-
|
|
190
|
-
def authenticate_service
|
|
191
|
-
token = request.headers['SECURE-TOKEN']&.sub(/^Bearer /, '')
|
|
192
|
-
@current_service = RobustServerSocket::ClientToken.new(token)
|
|
193
|
-
|
|
194
|
-
if @current_service.valid? # doesn't raise errors
|
|
195
|
-
# Token is valid
|
|
196
|
-
else
|
|
197
|
-
# Token is invalid
|
|
198
|
-
render json: { error: 'Unauthorized' }, status: :unauthorized
|
|
199
|
-
end
|
|
200
|
-
end
|
|
201
206
|
end
|
|
202
207
|
```
|
|
203
208
|
|
|
204
|
-
###
|
|
209
|
+
### valid? (non-raising)
|
|
205
210
|
|
|
206
211
|
```ruby
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
client_token = RobustServerSocket::ClientToken.new(token_string)
|
|
212
|
+
token = request.headers['SECURE-TOKEN']&.sub(/^Bearer /, '')
|
|
213
|
+
client_token = RobustServerSocket::ClientToken.new(token)
|
|
210
214
|
|
|
211
|
-
# Check validity (returns true/false)
|
|
212
215
|
if client_token.valid?
|
|
213
|
-
# Get client name
|
|
214
216
|
client_name = client_token.client
|
|
215
|
-
puts "Authorized client: #{client_name}"
|
|
216
217
|
else
|
|
217
|
-
# Token is invalid
|
|
218
218
|
render json: { error: 'Unauthorized' }, status: :unauthorized
|
|
219
219
|
end
|
|
220
|
-
|
|
221
|
-
# Quick validation with exceptions (recommended)
|
|
222
|
-
begin
|
|
223
|
-
service_token = RobustServerSocket::ClientToken.validate!(token_string)
|
|
224
|
-
client_name = service_token.client
|
|
225
|
-
rescue => e
|
|
226
|
-
# Handle specific errors
|
|
227
|
-
end
|
|
228
220
|
```
|
|
229
221
|
|
|
230
222
|
## ❌ Error Handling
|
|
231
223
|
|
|
232
224
|
### Exception Types
|
|
233
225
|
|
|
234
|
-
| Exception
|
|
235
|
-
|
|
236
|
-
| `InvalidToken`
|
|
237
|
-
| `UnauthorizedClient`
|
|
238
|
-
| `UsedToken`
|
|
239
|
-
| `StaleToken`
|
|
240
|
-
| `RateLimitExceeded`
|
|
226
|
+
| Exception | Reason | HTTP Status |
|
|
227
|
+
|------------------------------------------------------------|-------------------------------------------|-------------|
|
|
228
|
+
| `ClientToken::InvalidToken` | Token cannot be decrypted or wrong format | 401 |
|
|
229
|
+
| `Modules::ClientAuthProtection::UnauthorizedClient` | Client not in whitelist | 403 |
|
|
230
|
+
| `Modules::ReplayAttackProtection::UsedToken` | Token has already been used | 401 |
|
|
231
|
+
| `Modules::ReplayAttackProtection::StaleToken` | Token expired or from the future (>30s) | 401 |
|
|
232
|
+
| `RateLimiter::RateLimitExceeded` | Rate limit exceeded | 429 |
|
|
241
233
|
|
|
242
234
|
### Centralized Error Handling
|
|
243
235
|
|
|
244
236
|
```ruby
|
|
245
|
-
# In ApplicationController
|
|
246
237
|
rescue_from RobustServerSocket::ClientToken::InvalidToken,
|
|
247
|
-
RobustServerSocket::
|
|
248
|
-
RobustServerSocket::
|
|
238
|
+
RobustServerSocket::Modules::ReplayAttackProtection::UsedToken,
|
|
239
|
+
RobustServerSocket::Modules::ReplayAttackProtection::StaleToken,
|
|
249
240
|
with: :unauthorized_response
|
|
250
241
|
|
|
251
|
-
rescue_from RobustServerSocket::
|
|
242
|
+
rescue_from RobustServerSocket::Modules::ClientAuthProtection::UnauthorizedClient,
|
|
252
243
|
with: :forbidden_response
|
|
253
244
|
|
|
254
245
|
rescue_from RobustServerSocket::RateLimiter::RateLimitExceeded,
|
|
@@ -257,80 +248,24 @@ rescue_from RobustServerSocket::RateLimiter::RateLimitExceeded,
|
|
|
257
248
|
private
|
|
258
249
|
|
|
259
250
|
def unauthorized_response(exception)
|
|
260
|
-
render json: {
|
|
261
|
-
error: 'Authentication failed',
|
|
262
|
-
message: exception.message,
|
|
263
|
-
type: exception.class.name
|
|
264
|
-
}, status: :unauthorized
|
|
251
|
+
render json: { error: 'Authentication failed', message: exception.message }, status: :unauthorized
|
|
265
252
|
end
|
|
266
253
|
|
|
267
254
|
def forbidden_response(exception)
|
|
268
|
-
render json: {
|
|
269
|
-
error: 'Access denied',
|
|
270
|
-
message: exception.message,
|
|
271
|
-
type: exception.class.name
|
|
272
|
-
}, status: :forbidden
|
|
255
|
+
render json: { error: 'Access denied', message: exception.message }, status: :forbidden
|
|
273
256
|
end
|
|
274
257
|
|
|
275
258
|
def rate_limit_response(exception)
|
|
276
259
|
render json: {
|
|
277
260
|
error: 'Too many requests',
|
|
278
261
|
message: exception.message,
|
|
279
|
-
type: exception.class.name,
|
|
280
262
|
retry_after: RobustServerSocket.configuration.rate_limit_window_seconds
|
|
281
263
|
}, status: :too_many_requests
|
|
282
264
|
end
|
|
283
265
|
```
|
|
284
266
|
|
|
285
|
-
### 2. Redis Configuration
|
|
286
|
-
|
|
287
|
-
**✅ DO:**
|
|
288
|
-
```ruby
|
|
289
|
-
# Use separate namespace for each environment
|
|
290
|
-
c.redis_url = ENV.fetch('REDIS_URL', 'redis://localhost:6379/0')
|
|
291
|
-
|
|
292
|
-
# Configure connection pool in production
|
|
293
|
-
# In config/initializers/redis.rb
|
|
294
|
-
Redis.current = ConnectionPool.new(size: 5, timeout: 5) do
|
|
295
|
-
Redis.new(url: ENV['REDIS_URL'], password: ENV['REDIS_PASSWORD'])
|
|
296
|
-
end
|
|
297
|
-
|
|
298
|
-
# Monitor Redis status
|
|
299
|
-
# Use Redis Sentinel or Cluster for high availability
|
|
300
|
-
```
|
|
301
|
-
|
|
302
|
-
**❌ DON'T:**
|
|
303
|
-
```ruby
|
|
304
|
-
# DON'T use same Redis DB for all environments, use separate Redis DB
|
|
305
|
-
# DON'T ignore Redis errors (rate limiter is already fail-open, but log them)
|
|
306
|
-
```
|
|
307
|
-
|
|
308
|
-
### 5. Service Whitelist
|
|
309
|
-
|
|
310
|
-
```ruby
|
|
311
|
-
# Explicitly specify only necessary services
|
|
312
|
-
c.allowed_services = %w[core payments] # ✅
|
|
313
|
-
|
|
314
|
-
# DON'T use wildcards or regular expressions
|
|
315
|
-
c.allowed_services = %w[*] # ❌ DANGEROUS!
|
|
316
|
-
|
|
317
|
-
# Synchronize with client keychain
|
|
318
|
-
# Server (robust_server_socket):
|
|
319
|
-
c.allowed_services = %w[core]
|
|
320
|
-
|
|
321
|
-
# Client (robust_client_socket):
|
|
322
|
-
c.keychain = {
|
|
323
|
-
core: { # ← Must match
|
|
324
|
-
base_uri: 'https://core.example.com',
|
|
325
|
-
public_key: '-----BEGIN PUBLIC KEY-----...'
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
```
|
|
329
|
-
|
|
330
267
|
## 🤝 Integration with RobustClientSocket
|
|
331
268
|
|
|
332
|
-
For full functionality, configure the client side:
|
|
333
|
-
|
|
334
269
|
```ruby
|
|
335
270
|
# On client (RobustClientSocket)
|
|
336
271
|
RobustClientSocket.configure do |c|
|
|
@@ -338,270 +273,49 @@ RobustClientSocket.configure do |c|
|
|
|
338
273
|
c.keychain = {
|
|
339
274
|
payments: {
|
|
340
275
|
base_uri: 'https://payments.example.com',
|
|
341
|
-
public_key: '-----BEGIN PUBLIC KEY-----...'
|
|
276
|
+
public_key: '-----BEGIN PUBLIC KEY-----...'
|
|
342
277
|
}
|
|
343
278
|
}
|
|
344
279
|
end
|
|
345
280
|
|
|
346
281
|
# On server (RobustServerSocket)
|
|
347
282
|
RobustServerSocket.configure do |c|
|
|
348
|
-
c.allowed_services = %w[core]
|
|
349
|
-
c.private_key = '-----BEGIN PRIVATE KEY-----...'
|
|
283
|
+
c.allowed_services = %w[core]
|
|
284
|
+
c.private_key = '-----BEGIN PRIVATE KEY-----...'
|
|
350
285
|
end
|
|
351
286
|
```
|
|
352
287
|
|
|
353
|
-
##
|
|
354
|
-
|
|
355
|
-
- [RobustClientSocket documentation](https://github.com/tee0zed/robust_client_socket)
|
|
356
|
-
- [RSA encryption best practices](https://www.openssl.org/docs/)
|
|
357
|
-
- [Redis security guide](https://redis.io/topics/security)
|
|
358
|
-
|
|
359
|
-
## 📝 License
|
|
288
|
+
## 📊 Performance
|
|
360
289
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
## 🐛 Bugs and Suggestions
|
|
364
|
-
|
|
365
|
-
Report issues through your repository's issue tracker.
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
#### Test: 1000 Requests with Token Validation
|
|
290
|
+
### Benchmark: 1000 Requests with Token Validation
|
|
369
291
|
|
|
370
292
|
**Without RobustServerSocket (plain HTTP controller):**
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
1000.times do
|
|
374
|
-
# Regular request without authorization
|
|
375
|
-
get '/api/v1/partners/719a68e4-3457-45dd-8d7f-73f1d367b87a/merchants'
|
|
376
|
-
end
|
|
377
|
-
end
|
|
378
|
-
```
|
|
379
|
-
|
|
380
|
-
**Results (approximate):**
|
|
381
|
-
- **Real time**: ~2.5 seconds
|
|
382
|
-
- No token verification
|
|
383
|
-
- No RSA decryption
|
|
384
|
-
- No Redis checks
|
|
385
|
-
|
|
386
|
-
---
|
|
293
|
+
- Real time: ~2.5 seconds
|
|
294
|
+
- No token verification, no RSA decryption, no Redis checks
|
|
387
295
|
|
|
388
296
|
**With RobustServerSocket (full protection):**
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
1000.times do
|
|
392
|
-
# Request with RobustClientSocket (RSA + tokens)
|
|
393
|
-
RobustClientSocket::CoreApi.get('/api/v1/partners/719a68e4-3457-45dd-8d7f-73f1d367b87a/merchants')
|
|
394
|
-
end
|
|
395
|
-
end
|
|
396
|
-
```
|
|
397
|
-
|
|
398
|
-
**Results:**
|
|
399
|
-
- **Real time**: 2.77 seconds
|
|
400
|
-
- **User CPU**: 0.23 seconds
|
|
401
|
-
- **System CPU**: 0.54 seconds
|
|
402
|
-
- **Total CPU**: 0.77 seconds
|
|
403
|
-
|
|
404
|
-
### 📊 Security Overhead Analysis
|
|
405
|
-
|
|
406
|
-
| Operation | Time | % of Request |
|
|
407
|
-
|-----------|------|-------------|
|
|
408
|
-
| **RSA Decryption** | ~0.1-0.2ms | 3-7% |
|
|
409
|
-
| **Redis Token Check** | ~0.05-0.1ms | 2-3% |
|
|
410
|
-
| **Rate Limiting** | ~0.02-0.05ms | 1% |
|
|
411
|
-
| **Whitelist Validation** | <0.01ms | <1% |
|
|
412
|
-
| **Total Overhead** | **~0.2-0.4ms** | **~10-15%** |
|
|
413
|
-
|
|
414
|
-
### 🎯 Key Findings
|
|
415
|
-
|
|
416
|
-
1. **Minimal overhead (~0.3ms per request)**
|
|
417
|
-
- RSA-2048 decryption: ~0.15ms
|
|
418
|
-
- Redis operations: ~0.08ms
|
|
419
|
-
- Rate limiting: ~0.03ms
|
|
420
|
-
|
|
421
|
-
2. **Scales linearly**
|
|
422
|
-
- 100 req/s = +30ms overhead
|
|
423
|
-
- 1000 req/s = +300ms overhead
|
|
424
|
-
- Acceptable for most applications
|
|
425
|
-
|
|
426
|
-
3. **Redis is main bottleneck**
|
|
427
|
-
- Use Redis Sentinel/Cluster
|
|
428
|
-
- Connection pooling is critical
|
|
429
|
-
- Fail-open strategy for reliability
|
|
430
|
-
|
|
431
|
-
### 💡 Performance Optimization
|
|
432
|
-
|
|
433
|
-
**1. Redis Connection Pool:**
|
|
434
|
-
|
|
435
|
-
```ruby
|
|
436
|
-
# config/initializers/redis.rb
|
|
437
|
-
require 'connection_pool'
|
|
438
|
-
|
|
439
|
-
REDIS_POOL = ConnectionPool.new(size: 25, timeout: 5) do
|
|
440
|
-
Redis.new(
|
|
441
|
-
url: ENV['REDIS_URL'],
|
|
442
|
-
password: ENV['REDIS_PASSWORD'],
|
|
443
|
-
reconnect_attempts: 3,
|
|
444
|
-
reconnect_delay: 0.5,
|
|
445
|
-
reconnect_delay_max: 5.0
|
|
446
|
-
)
|
|
447
|
-
end
|
|
448
|
-
|
|
449
|
-
# In RobustServerSocket::Cacher
|
|
450
|
-
def self.with_redis
|
|
451
|
-
REDIS_POOL.with do |redis|
|
|
452
|
-
yield redis
|
|
453
|
-
end
|
|
454
|
-
end
|
|
455
|
-
```
|
|
456
|
-
|
|
457
|
-
**2. Public Key Caching:**
|
|
458
|
-
|
|
459
|
-
```ruby
|
|
460
|
-
# Keys are already cached at load!, but can be optimized
|
|
461
|
-
class RobustServerSocket::ClientToken
|
|
462
|
-
# Keys are loaded once at application startup
|
|
463
|
-
# No additional optimization needed
|
|
464
|
-
end
|
|
465
|
-
```
|
|
466
|
-
|
|
467
|
-
**3. Rate Limiting Optimization:**
|
|
468
|
-
|
|
469
|
-
```ruby
|
|
470
|
-
RobustServerSocket.configure do |c|
|
|
471
|
-
# For high-load systems
|
|
472
|
-
c.rate_limit_max_requests = 1000 # Increase limit
|
|
473
|
-
c.rate_limit_window_seconds = 60
|
|
474
|
-
|
|
475
|
-
# For low-load systems
|
|
476
|
-
c.rate_limit_max_requests = 100
|
|
477
|
-
c.rate_limit_window_seconds = 60
|
|
478
|
-
end
|
|
479
|
-
```
|
|
480
|
-
|
|
481
|
-
**4. Token Expiration Optimization:**
|
|
482
|
-
|
|
483
|
-
```ruby
|
|
484
|
-
# Short lifetime = more requests for new tokens
|
|
485
|
-
c.token_expiration_time = 10.minutes # ❌ High traffic
|
|
486
|
-
|
|
487
|
-
# Optimal time for inter-service calls
|
|
488
|
-
c.token_expiration_time = 3 # ✅ 3 seconds is enough
|
|
489
|
-
```
|
|
490
|
-
|
|
491
|
-
### 🔬 Performance Monitoring
|
|
492
|
-
|
|
493
|
-
**Metrics to Track:**
|
|
297
|
+
- Real time: ~2.77 seconds
|
|
298
|
+
- User CPU: 0.23s, System CPU: 0.54s
|
|
494
299
|
|
|
495
|
-
|
|
496
|
-
class ApiController < ApplicationController
|
|
497
|
-
around_action :track_auth_performance
|
|
498
|
-
|
|
499
|
-
private
|
|
500
|
-
|
|
501
|
-
def track_auth_performance
|
|
502
|
-
start = Time.now
|
|
503
|
-
|
|
504
|
-
begin
|
|
505
|
-
yield
|
|
506
|
-
ensure
|
|
507
|
-
duration = ((Time.now - start) * 1000).round(2)
|
|
508
|
-
|
|
509
|
-
# Total request time
|
|
510
|
-
Metrics.timing('request.duration', duration, tags: [
|
|
511
|
-
"controller:#{controller_name}",
|
|
512
|
-
"action:#{action_name}"
|
|
513
|
-
])
|
|
514
|
-
|
|
515
|
-
# Authentication attempts
|
|
516
|
-
if @current_service
|
|
517
|
-
Metrics.increment('auth.success', tags: ["service:#{@current_service.client}"])
|
|
518
|
-
else
|
|
519
|
-
Metrics.increment('auth.failure')
|
|
520
|
-
end
|
|
521
|
-
end
|
|
522
|
-
end
|
|
523
|
-
end
|
|
524
|
-
|
|
525
|
-
# Specific metrics for RobustServerSocket
|
|
526
|
-
module RobustServerSocket
|
|
527
|
-
class ClientToken
|
|
528
|
-
def self.validate_with_metrics!(token)
|
|
529
|
-
start = Time.now
|
|
530
|
-
result = validate!(token)
|
|
531
|
-
duration = ((Time.now - start) * 1000).round(2)
|
|
532
|
-
|
|
533
|
-
Metrics.timing('robust_server.validation.duration', duration)
|
|
534
|
-
result
|
|
535
|
-
rescue StandardError => e
|
|
536
|
-
Metrics.increment('robust_server.validation.error', tags: ["error:#{e.class.name}"])
|
|
537
|
-
raise
|
|
538
|
-
end
|
|
539
|
-
end
|
|
540
|
-
end
|
|
541
|
-
```
|
|
542
|
-
|
|
543
|
-
### 📈 Performance at Different Loads
|
|
544
|
-
|
|
545
|
-
| Req/s | Without Protection | With RobustServerSocket | Overhead | Acceptable |
|
|
546
|
-
|-------|-------------------|------------------------|----------|-----------|
|
|
547
|
-
| 10 | 100ms | 103ms | 3ms | ✅ Excellent |
|
|
548
|
-
| 100 | 1s | 1.03s | 30ms | ✅ Excellent |
|
|
549
|
-
| 500 | 5s | 5.15s | 150ms | ✅ Good |
|
|
550
|
-
| 1,000 | 10s | 10.3s | 300ms | ✅ Acceptable |
|
|
551
|
-
| 5,000 | 50s | 51.5s | 1.5s | ⚠️ Redis scaling needed |
|
|
552
|
-
| 10,000 | 100s | 103s | 3s | ⚠️ Need Redis Cluster |
|
|
553
|
-
|
|
554
|
-
**Conclusion:** Up to 1000 req/s - excellent performance. Higher loads require Redis scaling.
|
|
555
|
-
|
|
556
|
-
### 🚀 Production Recommendations
|
|
557
|
-
|
|
558
|
-
**For high-load systems (>1000 req/s):**
|
|
559
|
-
|
|
560
|
-
1. **Redis Cluster** - distributed load
|
|
561
|
-
2. **Connection Pool** - minimum 25-50 connections
|
|
562
|
-
3. **Monitor Redis** - latency, memory, connections
|
|
563
|
-
4. **Fail-over Strategy** - Redis Sentinel
|
|
564
|
-
5. **CDN for static** - reduce overall load
|
|
565
|
-
|
|
566
|
-
**For medium load (100-1000 req/s):**
|
|
567
|
-
|
|
568
|
-
1. **Standalone Redis** with persistence
|
|
569
|
-
2. **Connection Pool** - 10-25 connections
|
|
570
|
-
3. **Basic monitoring**
|
|
571
|
-
4. **Rate limiting** - spike protection
|
|
300
|
+
### Security Overhead Analysis
|
|
572
301
|
|
|
573
|
-
|
|
302
|
+
| Operation | Time | % of Request |
|
|
303
|
+
|------------------------|-------------|-------------|
|
|
304
|
+
| RSA Decryption | ~0.1–0.2ms | 3–7% |
|
|
305
|
+
| Redis Token Check | ~0.05–0.1ms | 2–3% |
|
|
306
|
+
| Rate Limiting | ~0.02–0.05ms| 1% |
|
|
307
|
+
| Whitelist Validation | <0.01ms | <1% |
|
|
308
|
+
| **Total Overhead** | **~0.2–0.4ms** | **~10–15%** |
|
|
574
309
|
|
|
575
|
-
|
|
576
|
-
2. **Default connection pool** (5)
|
|
577
|
-
3. **Standard configuration**
|
|
310
|
+
Up to 1000 req/s — excellent performance. Higher loads require Redis Sentinel/Cluster.
|
|
578
311
|
|
|
579
|
-
##
|
|
580
|
-
|
|
581
|
-
For full functionality, configure the client side:
|
|
312
|
+
## 🗺️ TODO
|
|
582
313
|
|
|
583
|
-
|
|
584
|
-
# On client (RobustClientSocket)
|
|
585
|
-
RobustClientSocket.configure do |c|
|
|
586
|
-
c.service_name = 'core' # ← Must be in server's allowed_services
|
|
587
|
-
c.keychain = {
|
|
588
|
-
payments: {
|
|
589
|
-
base_uri: 'https://payments.example.com',
|
|
590
|
-
public_key: '-----BEGIN PUBLIC KEY-----...' # Public key of payments server
|
|
591
|
-
}
|
|
592
|
-
}
|
|
593
|
-
end
|
|
594
|
-
|
|
595
|
-
# On server (RobustServerSocket)
|
|
596
|
-
RobustServerSocket.configure do |c|
|
|
597
|
-
c.allowed_services = %w[core] # ← Matches client's service_name
|
|
598
|
-
c.private_key = '-----BEGIN PRIVATE KEY-----...' # Private pair to public_key
|
|
599
|
-
end
|
|
600
|
-
```
|
|
314
|
+
- [ ] **Per-client rate limit keys** — configurable individual limits per `client_name` instead of a single global limit
|
|
601
315
|
|
|
602
316
|
## 📚 Additional Resources
|
|
603
317
|
|
|
604
|
-
- [RobustClientSocket
|
|
318
|
+
- [RobustClientSocket](https://github.com/tee0zed/robust_client_socket)
|
|
605
319
|
- [RSA encryption best practices](https://www.openssl.org/docs/)
|
|
606
320
|
- [Redis security guide](https://redis.io/topics/security)
|
|
607
321
|
|
|
@@ -611,4 +325,4 @@ See [MIT-LICENSE](MIT-LICENSE) file
|
|
|
611
325
|
|
|
612
326
|
## 🐛 Bugs and Suggestions
|
|
613
327
|
|
|
614
|
-
Report issues
|
|
328
|
+
Report issues via GitHub issues, or directly at Telegram @cruel_mango or email tee0zed@gmail.com
|