robust_server_socket 0.3.3 → 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 +170 -494
- data/README.md +156 -244
- data/Rakefile +4 -8
- data/lib/robust_server_socket/cacher.rb +142 -0
- data/lib/robust_server_socket/client_token.rb +18 -43
- data/lib/robust_server_socket/configuration.rb +35 -4
- data/lib/robust_server_socket/modules/client_auth_protection.rb +22 -0
- data/lib/robust_server_socket/modules/rate_limit_protection.rb +23 -0
- data/lib/robust_server_socket/modules/replay_attack_protection.rb +48 -0
- data/lib/robust_server_socket/rate_limiter.rb +13 -37
- data/lib/robust_server_socket/secure_token/decrypt.rb +4 -8
- data/lib/robust_server_socket.rb +23 -7
- data/lib/version.rb +1 -1
- data/robust_server_socket.gemspec +12 -12
- metadata +10 -5
- data/lib/robust_server_socket/secure_token/cacher.rb +0 -138
data/README.en.md
CHANGED
|
@@ -2,7 +2,72 @@
|
|
|
2
2
|
|
|
3
3
|
Gem for inter-service authorization, used in pair with RobustClientSocket
|
|
4
4
|
|
|
5
|
-
### ⚠️ Not Production Tested (yet)
|
|
5
|
+
### ⚠️ Not Production Tested (yet) but tested in staging environment
|
|
6
|
+
|
|
7
|
+
`Not vibecoded`
|
|
8
|
+
|
|
9
|
+
## WHY
|
|
10
|
+
|
|
11
|
+
### The Problem
|
|
12
|
+
|
|
13
|
+
When building microservice architecture, the server side faces:
|
|
14
|
+
|
|
15
|
+
- **Lack of verification**: How to verify that a request came from a trusted service?
|
|
16
|
+
- **Replay attacks**: Intercepted requests can be replayed
|
|
17
|
+
- **DDoS attacks**: Need to limit request frequency
|
|
18
|
+
- **Boilerplate code**: Repetitive validation logic in every service
|
|
19
|
+
|
|
20
|
+
#### Even if infrastructure is behind a DMZ in a private network, there is still room for SSRF or OpenRedirect attacks
|
|
21
|
+
|
|
22
|
+
### The Solution
|
|
23
|
+
|
|
24
|
+
RobustServerSocket provides:
|
|
25
|
+
|
|
26
|
+
- **RSA decryption**: Token authenticity verification
|
|
27
|
+
- **Client whitelist**: Only authorized services allowed
|
|
28
|
+
- **Replay protection**: Blacklist of used tokens in Redis
|
|
29
|
+
- **Rate limiting**: Sliding window per-client request limits
|
|
30
|
+
|
|
31
|
+
## HOW IT WORKS
|
|
32
|
+
|
|
33
|
+
### Architecture
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
Incoming request with Secure-Token
|
|
37
|
+
│
|
|
38
|
+
v
|
|
39
|
+
┌──────────────────────────────┐
|
|
40
|
+
│ RobustServerSocket │
|
|
41
|
+
│ │
|
|
42
|
+
│ 1. RSA Decrypt │
|
|
43
|
+
│ 2. Validate Format │
|
|
44
|
+
│ 3. Check Client Whitelist │
|
|
45
|
+
│ 4. Check Rate Limit │
|
|
46
|
+
│ 5. Check Token Reuse │
|
|
47
|
+
│ 6. Check Token Expiration │
|
|
48
|
+
└──────────────┬───────────────┘
|
|
49
|
+
│
|
|
50
|
+
┌────────┼────────┐
|
|
51
|
+
v v
|
|
52
|
+
✅ Success ❌ Error
|
|
53
|
+
(continue) (401/403/429)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Validation Flow
|
|
57
|
+
|
|
58
|
+
1. **Decryption**: Base64 decode → RSA decrypt with private key
|
|
59
|
+
2. **Parsing**: Extract `{client_name}_{timestamp_ms}` from token
|
|
60
|
+
3. **Whitelist**: Verify client_name is in `allowed_services`
|
|
61
|
+
4. **Rate limit**: Sliding window — check request count within `rate_limit_window_seconds`
|
|
62
|
+
5. **Replay check**: Verify token hasn't been used (Redis)
|
|
63
|
+
6. **Staleness**: Verify timestamp is current (with ±30s clock skew tolerance)
|
|
64
|
+
|
|
65
|
+
### Modular System
|
|
66
|
+
|
|
67
|
+
Checks are enabled via `using_modules`:
|
|
68
|
+
- `:client_auth_protection` — client whitelist
|
|
69
|
+
- `:replay_attack_protection` — prevent token reuse
|
|
70
|
+
- `:rate_limit_protection` — sliding window rate limiting
|
|
6
71
|
|
|
7
72
|
## 📋 Table of Contents
|
|
8
73
|
|
|
@@ -19,28 +84,25 @@ RobustServerSocket implements a multi-layered protection system for inter-servic
|
|
|
19
84
|
### 1. Cryptographic Protection
|
|
20
85
|
- **RSA-2048 Encryption**: Uses RSA key pairs with minimum 2048-bit length
|
|
21
86
|
- **Key Validation**: Automatic key size verification during configuration
|
|
22
|
-
- **Asymmetric Encryption**: Private key on server, public keys on clients
|
|
23
87
|
|
|
24
|
-
### 2.
|
|
25
|
-
- **
|
|
26
|
-
- **
|
|
27
|
-
- **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`
|
|
28
91
|
|
|
29
|
-
### 3.
|
|
30
|
-
- **
|
|
31
|
-
- **
|
|
32
|
-
- **
|
|
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`
|
|
33
97
|
|
|
34
|
-
### 4.
|
|
35
|
-
- **
|
|
36
|
-
- **
|
|
37
|
-
- **
|
|
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`
|
|
38
102
|
|
|
39
|
-
### 5.
|
|
40
|
-
- **
|
|
41
|
-
- **
|
|
42
|
-
- **Fail-open Strategy**: If Redis is unavailable, requests are allowed (for reliability)
|
|
43
|
-
- **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`**
|
|
44
106
|
|
|
45
107
|
### 6. Injection Protection
|
|
46
108
|
- **Input Validation**: Type, length, and format verification of tokens
|
|
@@ -49,16 +111,13 @@ RobustServerSocket implements a multi-layered protection system for inter-servic
|
|
|
49
111
|
|
|
50
112
|
## 📦 Installation
|
|
51
113
|
|
|
52
|
-
Add to Gemfile:
|
|
53
|
-
|
|
54
114
|
```ruby
|
|
55
115
|
gem 'robust_server_socket'
|
|
56
116
|
```
|
|
57
117
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
bundle install
|
|
118
|
+
and on the client:
|
|
119
|
+
```ruby
|
|
120
|
+
gem 'robust_client_socket'
|
|
62
121
|
```
|
|
63
122
|
|
|
64
123
|
## ⚙️ Configuration
|
|
@@ -67,198 +126,120 @@ Create file `config/initializers/robust_server_socket.rb`:
|
|
|
67
126
|
|
|
68
127
|
```ruby
|
|
69
128
|
RobustServerSocket.configure do |c|
|
|
70
|
-
|
|
71
|
-
|
|
129
|
+
c.using_modules = %i[
|
|
130
|
+
client_auth_protection
|
|
131
|
+
replay_attack_protection
|
|
132
|
+
rate_limit_protection
|
|
133
|
+
]
|
|
134
|
+
|
|
72
135
|
# Service private key (RSA-2048 or higher)
|
|
73
|
-
# IMPORTANT: Store in environment variables, DO NOT commit to git!
|
|
74
136
|
c.private_key = ENV['ROBUST_SERVER_PRIVATE_KEY']
|
|
75
|
-
|
|
76
|
-
# Token lifetime in seconds
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
137
|
+
|
|
138
|
+
# Token lifetime in seconds (must match TTL on client side)
|
|
139
|
+
c.token_expiration_time = 10
|
|
140
|
+
|
|
80
141
|
# List of allowed services (whitelist)
|
|
81
|
-
# Must match
|
|
142
|
+
# Must match service_name in RobustClientSocket
|
|
82
143
|
c.allowed_services = %w[core payments notifications]
|
|
83
|
-
|
|
84
|
-
# Redis for
|
|
144
|
+
|
|
145
|
+
# Redis for replay_attack_protection and rate_limit_protection
|
|
85
146
|
c.redis_url = ENV.fetch('REDIS_URL', 'redis://localhost:6379/0')
|
|
86
|
-
c.redis_pass = ENV['REDIS_PASSWORD']
|
|
87
|
-
|
|
88
|
-
#
|
|
89
|
-
|
|
90
|
-
#
|
|
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
|
|
147
|
+
c.redis_pass = ENV['REDIS_PASSWORD']
|
|
148
|
+
|
|
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)
|
|
98
152
|
end
|
|
99
153
|
|
|
100
|
-
# Load configuration with validation
|
|
101
154
|
RobustServerSocket.load!
|
|
102
155
|
```
|
|
103
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
|
+
|
|
104
182
|
## 🚀 Usage
|
|
105
183
|
|
|
106
184
|
### Basic Authorization
|
|
107
185
|
|
|
108
186
|
```ruby
|
|
109
|
-
# In controller or middleware
|
|
110
187
|
class ApiController < ApplicationController
|
|
111
188
|
before_action :authenticate_service!
|
|
112
|
-
|
|
189
|
+
|
|
113
190
|
private
|
|
114
|
-
|
|
191
|
+
|
|
115
192
|
def authenticate_service!
|
|
116
|
-
# Header configured in RobustClientSocket (SECURE-TOKEN default)
|
|
117
193
|
token = request.headers['SECURE-TOKEN']&.sub(/^Bearer /, '')
|
|
118
|
-
|
|
119
|
-
@current_service = RobustServerSocket::ClientToken.validate!(token) # bang method (raises errors)
|
|
194
|
+
@current_service = RobustServerSocket::ClientToken.validate!(token)
|
|
120
195
|
rescue RobustServerSocket::ClientToken::InvalidToken
|
|
121
196
|
render json: { error: 'Invalid token' }, status: :unauthorized
|
|
122
|
-
rescue RobustServerSocket::
|
|
197
|
+
rescue RobustServerSocket::Modules::ClientAuthProtection::UnauthorizedClient
|
|
123
198
|
render json: { error: 'Unauthorized service' }, status: :forbidden
|
|
124
|
-
rescue RobustServerSocket::
|
|
199
|
+
rescue RobustServerSocket::Modules::ReplayAttackProtection::UsedToken
|
|
125
200
|
render json: { error: 'Token already used' }, status: :unauthorized
|
|
126
|
-
rescue RobustServerSocket::
|
|
201
|
+
rescue RobustServerSocket::Modules::ReplayAttackProtection::StaleToken
|
|
127
202
|
render json: { error: 'Token expired' }, status: :unauthorized
|
|
128
203
|
rescue RobustServerSocket::RateLimiter::RateLimitExceeded => e
|
|
129
204
|
render json: { error: e.message }, status: :too_many_requests
|
|
130
205
|
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
206
|
end
|
|
144
207
|
```
|
|
145
208
|
|
|
146
|
-
###
|
|
209
|
+
### valid? (non-raising)
|
|
147
210
|
|
|
148
211
|
```ruby
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
client_token = RobustServerSocket::ClientToken.new(token_string)
|
|
212
|
+
token = request.headers['SECURE-TOKEN']&.sub(/^Bearer /, '')
|
|
213
|
+
client_token = RobustServerSocket::ClientToken.new(token)
|
|
152
214
|
|
|
153
|
-
# Check validity (returns true/false)
|
|
154
215
|
if client_token.valid?
|
|
155
|
-
# Get client name
|
|
156
216
|
client_name = client_token.client
|
|
157
|
-
puts "Authorized client: #{client_name}"
|
|
158
217
|
else
|
|
159
|
-
# Token is invalid
|
|
160
218
|
render json: { error: 'Unauthorized' }, status: :unauthorized
|
|
161
219
|
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
220
|
```
|
|
239
221
|
|
|
240
222
|
## ❌ Error Handling
|
|
241
223
|
|
|
242
224
|
### Exception Types
|
|
243
225
|
|
|
244
|
-
| Exception
|
|
245
|
-
|
|
246
|
-
| `InvalidToken`
|
|
247
|
-
| `UnauthorizedClient`
|
|
248
|
-
| `UsedToken`
|
|
249
|
-
| `StaleToken`
|
|
250
|
-
| `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 |
|
|
251
233
|
|
|
252
234
|
### Centralized Error Handling
|
|
253
235
|
|
|
254
236
|
```ruby
|
|
255
|
-
# In ApplicationController
|
|
256
237
|
rescue_from RobustServerSocket::ClientToken::InvalidToken,
|
|
257
|
-
RobustServerSocket::
|
|
258
|
-
RobustServerSocket::
|
|
238
|
+
RobustServerSocket::Modules::ReplayAttackProtection::UsedToken,
|
|
239
|
+
RobustServerSocket::Modules::ReplayAttackProtection::StaleToken,
|
|
259
240
|
with: :unauthorized_response
|
|
260
241
|
|
|
261
|
-
rescue_from RobustServerSocket::
|
|
242
|
+
rescue_from RobustServerSocket::Modules::ClientAuthProtection::UnauthorizedClient,
|
|
262
243
|
with: :forbidden_response
|
|
263
244
|
|
|
264
245
|
rescue_from RobustServerSocket::RateLimiter::RateLimitExceeded,
|
|
@@ -267,106 +248,24 @@ rescue_from RobustServerSocket::RateLimiter::RateLimitExceeded,
|
|
|
267
248
|
private
|
|
268
249
|
|
|
269
250
|
def unauthorized_response(exception)
|
|
270
|
-
render json: {
|
|
271
|
-
error: 'Authentication failed',
|
|
272
|
-
message: exception.message,
|
|
273
|
-
type: exception.class.name
|
|
274
|
-
}, status: :unauthorized
|
|
251
|
+
render json: { error: 'Authentication failed', message: exception.message }, status: :unauthorized
|
|
275
252
|
end
|
|
276
253
|
|
|
277
254
|
def forbidden_response(exception)
|
|
278
|
-
render json: {
|
|
279
|
-
error: 'Access denied',
|
|
280
|
-
message: exception.message,
|
|
281
|
-
type: exception.class.name
|
|
282
|
-
}, status: :forbidden
|
|
255
|
+
render json: { error: 'Access denied', message: exception.message }, status: :forbidden
|
|
283
256
|
end
|
|
284
257
|
|
|
285
258
|
def rate_limit_response(exception)
|
|
286
259
|
render json: {
|
|
287
260
|
error: 'Too many requests',
|
|
288
261
|
message: exception.message,
|
|
289
|
-
type: exception.class.name,
|
|
290
262
|
retry_after: RobustServerSocket.configuration.rate_limit_window_seconds
|
|
291
263
|
}, status: :too_many_requests
|
|
292
264
|
end
|
|
293
265
|
```
|
|
294
266
|
|
|
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
267
|
## 🤝 Integration with RobustClientSocket
|
|
367
268
|
|
|
368
|
-
For full functionality, configure the client side:
|
|
369
|
-
|
|
370
269
|
```ruby
|
|
371
270
|
# On client (RobustClientSocket)
|
|
372
271
|
RobustClientSocket.configure do |c|
|
|
@@ -374,272 +273,49 @@ RobustClientSocket.configure do |c|
|
|
|
374
273
|
c.keychain = {
|
|
375
274
|
payments: {
|
|
376
275
|
base_uri: 'https://payments.example.com',
|
|
377
|
-
public_key: '-----BEGIN PUBLIC KEY-----...'
|
|
276
|
+
public_key: '-----BEGIN PUBLIC KEY-----...'
|
|
378
277
|
}
|
|
379
278
|
}
|
|
380
279
|
end
|
|
381
280
|
|
|
382
281
|
# On server (RobustServerSocket)
|
|
383
282
|
RobustServerSocket.configure do |c|
|
|
384
|
-
c.allowed_services = %w[core]
|
|
385
|
-
c.private_key = '-----BEGIN PRIVATE KEY-----...'
|
|
283
|
+
c.allowed_services = %w[core]
|
|
284
|
+
c.private_key = '-----BEGIN PRIVATE KEY-----...'
|
|
386
285
|
end
|
|
387
286
|
```
|
|
388
287
|
|
|
389
|
-
##
|
|
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.
|
|
288
|
+
## 📊 Performance
|
|
402
289
|
|
|
403
|
-
|
|
404
|
-
#### Test: 1000 Requests with Token Validation
|
|
290
|
+
### Benchmark: 1000 Requests with Token Validation
|
|
405
291
|
|
|
406
292
|
**Without RobustServerSocket (plain HTTP controller):**
|
|
407
|
-
|
|
408
|
-
|
|
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
|
-
---
|
|
293
|
+
- Real time: ~2.5 seconds
|
|
294
|
+
- No token verification, no RSA decryption, no Redis checks
|
|
423
295
|
|
|
424
296
|
**With RobustServerSocket (full protection):**
|
|
425
|
-
|
|
426
|
-
|
|
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
|
-
```
|
|
297
|
+
- Real time: ~2.77 seconds
|
|
298
|
+
- User CPU: 0.23s, System CPU: 0.54s
|
|
579
299
|
|
|
580
|
-
###
|
|
300
|
+
### Security Overhead Analysis
|
|
581
301
|
|
|
582
|
-
|
|
|
583
|
-
|
|
584
|
-
|
|
|
585
|
-
|
|
|
586
|
-
|
|
|
587
|
-
|
|
|
588
|
-
|
|
|
589
|
-
| 10,000 | 100s | 103s | 3s | ⚠️ Need Redis Cluster |
|
|
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%** |
|
|
590
309
|
|
|
591
|
-
|
|
310
|
+
Up to 1000 req/s — excellent performance. Higher loads require Redis Sentinel/Cluster.
|
|
592
311
|
|
|
593
|
-
|
|
312
|
+
## 🗺️ TODO
|
|
594
313
|
|
|
595
|
-
**
|
|
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
|
-
```
|
|
314
|
+
- [ ] **Per-client rate limit keys** — configurable individual limits per `client_name` instead of a single global limit
|
|
638
315
|
|
|
639
316
|
## 📚 Additional Resources
|
|
640
317
|
|
|
641
|
-
- [
|
|
642
|
-
- [RobustClientSocket documentation](../robust_client_socket/README.md)
|
|
318
|
+
- [RobustClientSocket](https://github.com/tee0zed/robust_client_socket)
|
|
643
319
|
- [RSA encryption best practices](https://www.openssl.org/docs/)
|
|
644
320
|
- [Redis security guide](https://redis.io/topics/security)
|
|
645
321
|
|
|
@@ -649,4 +325,4 @@ See [MIT-LICENSE](MIT-LICENSE) file
|
|
|
649
325
|
|
|
650
326
|
## 🐛 Bugs and Suggestions
|
|
651
327
|
|
|
652
|
-
Report issues
|
|
328
|
+
Report issues via GitHub issues, or directly at Telegram @cruel_mango or email tee0zed@gmail.com
|