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.md
CHANGED
|
@@ -1,8 +1,73 @@
|
|
|
1
1
|
# RobustServerSocket
|
|
2
2
|
|
|
3
|
-
Gem для межсервисной
|
|
3
|
+
Gem для межсервисной аутентификации, используется в паре с 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
|
+
### Проблема
|
|
12
|
+
|
|
13
|
+
При построении микросервисной архитектуры серверная сторона сталкивается с:
|
|
14
|
+
|
|
15
|
+
- **Отсутствием верификации**: Как проверить, что запрос пришёл от доверенного сервиса?
|
|
16
|
+
- **Replay-атаками**: Перехваченные запросы могут быть повторены
|
|
17
|
+
- **DDoS-атаками**: Необходимость ограничения частоты запросов
|
|
18
|
+
- **Boilerplate кодом**: Повторяющаяся логика валидации в каждом сервисе
|
|
19
|
+
|
|
20
|
+
#### Даже если инфраструктура находится за DMZ, в своей локальной сети, остаётся пространство для SSRF или OpenRedirect атак
|
|
21
|
+
|
|
22
|
+
### Решение
|
|
23
|
+
|
|
24
|
+
RobustServerSocket предоставляет:
|
|
25
|
+
|
|
26
|
+
- **RSA-дешифрование**: Проверка подлинности токенов
|
|
27
|
+
- **Whitelist клиентов**: Только разрешённые сервисы
|
|
28
|
+
- **Защиту от replay**: Блэклист использованных токенов в Redis
|
|
29
|
+
- **Rate limiting**: Скользящее окно запросов на клиента
|
|
30
|
+
|
|
31
|
+
## КАК ЭТО РАБОТАЕТ (HOW)
|
|
32
|
+
|
|
33
|
+
### Архитектура
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
Входящий запрос с 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
|
+
### Поток валидации
|
|
57
|
+
|
|
58
|
+
1. **Расшифровка**: Base64 decode → RSA decrypt с приватным ключом
|
|
59
|
+
2. **Парсинг**: Извлечение `{client_name}_{timestamp_ms}` из токена
|
|
60
|
+
3. **Whitelist**: Проверка client_name в `allowed_services`
|
|
61
|
+
4. **Rate limit**: Скользящее окно — проверка количества запросов за `rate_limit_window_seconds`
|
|
62
|
+
5. **Replay check**: Проверка, что токен не использован (Redis)
|
|
63
|
+
6. **Staleness**: Проверка timestamp на актуальность (с допуском ±30 секунд на рассинхрон часов)
|
|
64
|
+
|
|
65
|
+
### Модульная система
|
|
66
|
+
|
|
67
|
+
Проверки подключаются через `using_modules`:
|
|
68
|
+
- `:client_auth_protection` — whitelist клиентов
|
|
69
|
+
- `:replay_attack_protection` — защита от повторного использования токена
|
|
70
|
+
- `:rate_limit_protection` — rate limiting по скользящему окну
|
|
6
71
|
|
|
7
72
|
## 📋 Содержание
|
|
8
73
|
|
|
@@ -19,33 +84,25 @@ RobustServerSocket реализует многоуровневую систем
|
|
|
19
84
|
### 1. Криптографическая защита
|
|
20
85
|
- **RSA-2048 шифрование**: Используется пара ключей RSA с минимальной длиной 2048 бит
|
|
21
86
|
- **Валидация ключей**: Автоматическая проверка размера ключа при конфигурации
|
|
22
|
-
- **Асимметричное шифрование**: Приватный ключ на сервере, публичный — у клиентов
|
|
23
87
|
|
|
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 клиентов**: Только авторизованные сервисы могут подключаться
|
|
88
|
+
### 2. Контроль доступа
|
|
89
|
+
- **Whitelist клиентов**: Только авторизованные сервисы могут подключаться, при включенном модуле `:client_auth_protection`
|
|
36
90
|
- **Идентификация по имени**: Каждый клиент должен быть явно указан в `allowed_services`
|
|
37
|
-
- **Валидация формата токена**: Строгая проверка структуры токена
|
|
38
91
|
|
|
39
|
-
###
|
|
40
|
-
- **Защита от
|
|
41
|
-
- **
|
|
42
|
-
-
|
|
43
|
-
- **
|
|
92
|
+
### 3. Защита от перехвата токенов (replay-attack)
|
|
93
|
+
- **Защита от replay-attack**: использованные токены добавляются в черный список, при включенном модуле `:replay_attack_protection`
|
|
94
|
+
- **Staleness**: Токены автоматически становятся недействительными после истечения `token_expiration_time`
|
|
95
|
+
- **Допуск на рассинхрон часов**: ±30 секунд (CLOCK_SKEW)
|
|
96
|
+
- **TTL блэклиста**: вычисляется автоматически как `token_expiration_time + CLOCK_SKEW`
|
|
97
|
+
|
|
98
|
+
### 4. Rate limiting
|
|
99
|
+
- **Скользящее окно**: при включенном модуле `:rate_limit_protection` — точный подсчёт запросов без граничного burst-эффекта фиксированного окна
|
|
100
|
+
- **Fail-open стратегия**: если Redis недоступен, запросы пропускаются (для надёжности сервиса)
|
|
101
|
+
- **Изоляция по клиентам**: лимит считается отдельно для каждого `client_name`
|
|
44
102
|
|
|
45
|
-
###
|
|
46
|
-
-
|
|
47
|
-
-
|
|
48
|
-
- **Проверка на пустые значения**: Отклонение пустых или некорректных токенов
|
|
103
|
+
### 5. Защита от SSL stripping MITM attack
|
|
104
|
+
- **Принудительное HTTPS на сервере**: Все запросы должны быть совершены по HTTPS, чтобы защитить токены от перехвата
|
|
105
|
+
- **Включается на RobustClientSocket, ключём `ssl_verify: true`**
|
|
49
106
|
|
|
50
107
|
## 📦 Установка
|
|
51
108
|
|
|
@@ -53,197 +110,131 @@ RobustServerSocket реализует многоуровневую систем
|
|
|
53
110
|
gem 'robust_server_socket'
|
|
54
111
|
```
|
|
55
112
|
|
|
113
|
+
и на клиенте:
|
|
114
|
+
```ruby
|
|
115
|
+
gem 'robust_client_socket'
|
|
116
|
+
```
|
|
117
|
+
|
|
56
118
|
## ⚙️ Конфигурация
|
|
57
119
|
|
|
58
120
|
Создайте файл `config/initializers/robust_server_socket.rb`:
|
|
59
121
|
|
|
60
122
|
```ruby
|
|
61
123
|
RobustServerSocket.configure do |c|
|
|
62
|
-
|
|
63
|
-
|
|
124
|
+
c.using_modules = %i[
|
|
125
|
+
client_auth_protection
|
|
126
|
+
replay_attack_protection
|
|
127
|
+
rate_limit_protection
|
|
128
|
+
]
|
|
129
|
+
|
|
64
130
|
# Приватный ключ сервиса (RSA-2048 или выше)
|
|
65
131
|
c.private_key = ENV['ROBUST_SERVER_PRIVATE_KEY']
|
|
66
|
-
|
|
67
|
-
|
|
132
|
+
|
|
133
|
+
# Время жизни токена в секундах (должно совпадать с TTL на клиенте)
|
|
134
|
+
c.token_expiration_time = 10
|
|
135
|
+
|
|
68
136
|
# Список разрешённых сервисов (whitelist)
|
|
69
|
-
# Должен совпадать с
|
|
137
|
+
# Должен совпадать с service_name RobustClientSocket клиента
|
|
70
138
|
c.allowed_services = %w[core payments notifications]
|
|
71
|
-
|
|
72
|
-
# Redis для
|
|
139
|
+
|
|
140
|
+
# Redis для replay_attack_protection и rate_limit_protection
|
|
73
141
|
c.redis_url = ENV.fetch('REDIS_URL', 'redis://localhost:6379/0')
|
|
74
142
|
c.redis_pass = ENV['REDIS_PASSWORD']
|
|
75
143
|
|
|
76
|
-
#
|
|
77
|
-
|
|
78
|
-
c.
|
|
79
|
-
# Максимальное количество запросов в окне времени (по умолчанию: 100)
|
|
80
|
-
c.rate_limit_max_requests = 100
|
|
81
|
-
# Размер временного окна в секундах (по умолчанию: 60)
|
|
82
|
-
c.rate_limit_window_seconds = 60
|
|
144
|
+
# rate_limit_protection
|
|
145
|
+
c.rate_limit_max_requests = 100 # максимум запросов в окне (по умолчанию: 100)
|
|
146
|
+
c.rate_limit_window_seconds = 60 # размер окна в секундах (по умолчанию: 60)
|
|
83
147
|
end
|
|
84
148
|
|
|
85
|
-
# Загрузка конфигурации с валидацией
|
|
86
149
|
RobustServerSocket.load!
|
|
87
150
|
```
|
|
88
151
|
|
|
89
|
-
### Опции конфигурации
|
|
152
|
+
### Опции конфигурации
|
|
153
|
+
|
|
154
|
+
| Параметр | Тип | Обязательный | Default | Описание |
|
|
155
|
+
|-----------------------------|---------|-------------|-------------------------------------------------------------------------------------|-------------------------------------------------|
|
|
156
|
+
| `private_key` | String | ✅ | — | Приватный RSA ключ сервиса (RSA-2048 или выше) |
|
|
157
|
+
| `token_expiration_time` | Integer | ✅ | 10 | Время жизни токена в секундах |
|
|
158
|
+
| `allowed_services` | Array | ✅ | — | Список разрешённых сервисов (whitelist) |
|
|
159
|
+
| `redis_url` | String | ✅ | — | URL для подключения к Redis |
|
|
160
|
+
| `redis_pass` | String | ❌ | nil | Пароль для Redis |
|
|
161
|
+
| `using_modules` | Array | ❌ | `[:client_auth_protection, :rate_limit_protection, :replay_attack_protection]` | Используемые модули |
|
|
162
|
+
| `rate_limit_max_requests` | Integer | ❌ | 100 | Максимальное количество запросов в окне |
|
|
163
|
+
| `rate_limit_window_seconds` | Integer | ❌ | 60 | Размер временного окна в секундах |
|
|
164
|
+
|
|
165
|
+
> `store_used_token_time` больше не является конфигурируемым — вычисляется автоматически как `token_expiration_time + 30` (CLOCK_SKEW).
|
|
90
166
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
| `rate_limit_window_seconds` | Integer | ❌ | 60 | Размер временного окна в секундах |
|
|
167
|
+
### Совместимость с RobustClientSocket
|
|
168
|
+
|
|
169
|
+
Токен содержит таймстамп в **миллисекундах**. RobustClientSocket начиная с версии X.X должен генерировать:
|
|
170
|
+
|
|
171
|
+
```ruby
|
|
172
|
+
Process.clock_gettime(Process::CLOCK_REALTIME, :millisecond)
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Устаревший `Time.now.utc.to_i` (секунды) приведёт к тому, что все токены будут отклонены как `stale`.
|
|
101
176
|
|
|
102
177
|
## 🚀 Использование
|
|
103
178
|
|
|
104
179
|
### Базовая авторизация
|
|
105
180
|
|
|
106
181
|
```ruby
|
|
107
|
-
# В контроллере или middleware
|
|
108
182
|
class ApiController < ApplicationController
|
|
109
183
|
before_action :authenticate_service!
|
|
110
|
-
|
|
184
|
+
|
|
111
185
|
private
|
|
112
|
-
|
|
186
|
+
|
|
113
187
|
def authenticate_service!
|
|
114
|
-
# Хедер, прописанный в RobustClientSocket (SECURE-TOKEN default)
|
|
115
188
|
token = request.headers['SECURE-TOKEN']&.sub(/^Bearer /, '')
|
|
116
|
-
|
|
117
|
-
@current_service = RobustServerSocket::ClientToken.validate!(token) # bang method (рейзит ошибки)
|
|
189
|
+
@current_service = RobustServerSocket::ClientToken.validate!(token)
|
|
118
190
|
rescue RobustServerSocket::ClientToken::InvalidToken
|
|
119
191
|
render json: { error: 'Invalid token' }, status: :unauthorized
|
|
120
|
-
rescue RobustServerSocket::
|
|
192
|
+
rescue RobustServerSocket::Modules::ClientAuthProtection::UnauthorizedClient
|
|
121
193
|
render json: { error: 'Unauthorized service' }, status: :forbidden
|
|
122
|
-
rescue RobustServerSocket::
|
|
194
|
+
rescue RobustServerSocket::Modules::ReplayAttackProtection::UsedToken
|
|
123
195
|
render json: { error: 'Token already used' }, status: :unauthorized
|
|
124
|
-
rescue RobustServerSocket::
|
|
196
|
+
rescue RobustServerSocket::Modules::ReplayAttackProtection::StaleToken
|
|
125
197
|
render json: { error: 'Token expired' }, status: :unauthorized
|
|
126
198
|
rescue RobustServerSocket::RateLimiter::RateLimitExceeded => e
|
|
127
199
|
render json: { error: e.message }, status: :too_many_requests
|
|
128
200
|
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
201
|
end
|
|
142
202
|
```
|
|
143
203
|
|
|
144
|
-
###
|
|
204
|
+
### valid? (не рейзит)
|
|
145
205
|
|
|
146
206
|
```ruby
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
client_token = RobustServerSocket::ClientToken.new(token_string)
|
|
207
|
+
token = request.headers['SECURE-TOKEN']&.sub(/^Bearer /, '')
|
|
208
|
+
client_token = RobustServerSocket::ClientToken.new(token)
|
|
150
209
|
|
|
151
|
-
# Проверка валидности (возвращает true/false)
|
|
152
210
|
if client_token.valid?
|
|
153
|
-
# Получение имени клиента
|
|
154
211
|
client_name = client_token.client
|
|
155
|
-
puts "Authorized client: #{client_name}"
|
|
156
212
|
else
|
|
157
|
-
# Токен невалиден
|
|
158
213
|
render json: { error: 'Unauthorized' }, status: :unauthorized
|
|
159
214
|
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
215
|
```
|
|
224
216
|
|
|
225
217
|
## ❌ Обработка ошибок
|
|
226
218
|
|
|
227
219
|
### Типы исключений
|
|
228
220
|
|
|
229
|
-
| Исключение
|
|
230
|
-
|
|
231
|
-
| `InvalidToken`
|
|
232
|
-
| `UnauthorizedClient`
|
|
233
|
-
| `UsedToken`
|
|
234
|
-
| `StaleToken`
|
|
235
|
-
| `RateLimitExceeded`
|
|
221
|
+
| Исключение | Причина | HTTP статус |
|
|
222
|
+
|---------------------------------------------------------|----------------------------------------------|-------------|
|
|
223
|
+
| `ClientToken::InvalidToken` | Токен не расшифрован или неверный формат | 401 |
|
|
224
|
+
| `Modules::ClientAuthProtection::UnauthorizedClient` | Клиент не в whitelist | 403 |
|
|
225
|
+
| `Modules::ReplayAttackProtection::UsedToken` | Токен уже был использован | 401 |
|
|
226
|
+
| `Modules::ReplayAttackProtection::StaleToken` | Токен истёк или из будущего (>30s) | 401 |
|
|
227
|
+
| `RateLimiter::RateLimitExceeded` | Превышен лимит запросов | 429 |
|
|
236
228
|
|
|
237
229
|
### Централизованная обработка
|
|
238
230
|
|
|
239
231
|
```ruby
|
|
240
|
-
# В ApplicationController
|
|
241
232
|
rescue_from RobustServerSocket::ClientToken::InvalidToken,
|
|
242
|
-
RobustServerSocket::
|
|
243
|
-
RobustServerSocket::
|
|
233
|
+
RobustServerSocket::Modules::ReplayAttackProtection::UsedToken,
|
|
234
|
+
RobustServerSocket::Modules::ReplayAttackProtection::StaleToken,
|
|
244
235
|
with: :unauthorized_response
|
|
245
236
|
|
|
246
|
-
rescue_from RobustServerSocket::
|
|
237
|
+
rescue_from RobustServerSocket::Modules::ClientAuthProtection::UnauthorizedClient,
|
|
247
238
|
with: :forbidden_response
|
|
248
239
|
|
|
249
240
|
rescue_from RobustServerSocket::RateLimiter::RateLimitExceeded,
|
|
@@ -252,106 +243,24 @@ rescue_from RobustServerSocket::RateLimiter::RateLimitExceeded,
|
|
|
252
243
|
private
|
|
253
244
|
|
|
254
245
|
def unauthorized_response(exception)
|
|
255
|
-
render json: {
|
|
256
|
-
error: 'Authentication failed',
|
|
257
|
-
message: exception.message,
|
|
258
|
-
type: exception.class.name
|
|
259
|
-
}, status: :unauthorized
|
|
246
|
+
render json: { error: 'Authentication failed', message: exception.message }, status: :unauthorized
|
|
260
247
|
end
|
|
261
248
|
|
|
262
249
|
def forbidden_response(exception)
|
|
263
|
-
render json: {
|
|
264
|
-
error: 'Access denied',
|
|
265
|
-
message: exception.message,
|
|
266
|
-
type: exception.class.name
|
|
267
|
-
}, status: :forbidden
|
|
250
|
+
render json: { error: 'Access denied', message: exception.message }, status: :forbidden
|
|
268
251
|
end
|
|
269
252
|
|
|
270
253
|
def rate_limit_response(exception)
|
|
271
254
|
render json: {
|
|
272
255
|
error: 'Too many requests',
|
|
273
256
|
message: exception.message,
|
|
274
|
-
type: exception.class.name,
|
|
275
257
|
retry_after: RobustServerSocket.configuration.rate_limit_window_seconds
|
|
276
258
|
}, status: :too_many_requests
|
|
277
259
|
end
|
|
278
260
|
```
|
|
279
261
|
|
|
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
|
|
307
|
-
|
|
308
|
-
**✅ DO:**
|
|
309
|
-
```ruby
|
|
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
|
-
```
|
|
322
|
-
|
|
323
|
-
**❌ DON'T:**
|
|
324
|
-
```ruby
|
|
325
|
-
# НЕ используйте одну БД Redis для всех окружений, используйте отдельную bd redis
|
|
326
|
-
# НЕ игнорируйте ошибки Redis (rate limiter уже fail-open, но логируйте их)
|
|
327
|
-
```
|
|
328
|
-
|
|
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
262
|
## 🤝 Интеграция с RobustClientSocket
|
|
352
263
|
|
|
353
|
-
Для полноценной работы необходимо настроить клиентскую часть:
|
|
354
|
-
|
|
355
264
|
```ruby
|
|
356
265
|
# На клиенте (RobustClientSocket)
|
|
357
266
|
RobustClientSocket.configure do |c|
|
|
@@ -359,22 +268,25 @@ RobustClientSocket.configure do |c|
|
|
|
359
268
|
c.keychain = {
|
|
360
269
|
payments: {
|
|
361
270
|
base_uri: 'https://payments.example.com',
|
|
362
|
-
public_key: '-----BEGIN PUBLIC KEY-----...'
|
|
271
|
+
public_key: '-----BEGIN PUBLIC KEY-----...'
|
|
363
272
|
}
|
|
364
273
|
}
|
|
365
274
|
end
|
|
366
275
|
|
|
367
276
|
# На сервере (RobustServerSocket)
|
|
368
277
|
RobustServerSocket.configure do |c|
|
|
369
|
-
c.allowed_services = %w[core]
|
|
370
|
-
c.private_key = '-----BEGIN PRIVATE KEY-----...'
|
|
278
|
+
c.allowed_services = %w[core]
|
|
279
|
+
c.private_key = '-----BEGIN PRIVATE KEY-----...'
|
|
371
280
|
end
|
|
372
281
|
```
|
|
373
282
|
|
|
283
|
+
## 🗺️ TODO
|
|
284
|
+
|
|
285
|
+
- [ ] **Per-client ключи для rate limiter** — настраиваемые индивидуальные лимиты для каждого `client_name` вместо единого глобального лимита
|
|
286
|
+
|
|
374
287
|
## 📚 Дополнительные ресурсы
|
|
375
288
|
|
|
376
|
-
- [
|
|
377
|
-
- [RobustClientSocket documentation](https://github.com/tee0zed/robust_client_socket)
|
|
289
|
+
- [RobustClientSocket](https://github.com/tee0zed/robust_client_socket)
|
|
378
290
|
- [RSA encryption best practices](https://www.openssl.org/docs/)
|
|
379
291
|
- [Redis security guide](https://redis.io/topics/security)
|
|
380
292
|
|
|
@@ -384,4 +296,4 @@ end
|
|
|
384
296
|
|
|
385
297
|
## 🐛 Баги и предложения
|
|
386
298
|
|
|
387
|
-
Сообщайте о
|
|
299
|
+
Сообщайте о багах через ишью, или напрямую тг @cruel_mango или email tee0zed@gmail.com
|
data/Rakefile
CHANGED
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
3
|
+
require 'bundler/gem_tasks'
|
|
4
|
+
require 'rspec/core/rake_task'
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
t.libs << "test"
|
|
8
|
-
t.libs << "lib"
|
|
9
|
-
t.test_files = FileList["test/**/test_*.rb"]
|
|
10
|
-
end
|
|
6
|
+
RSpec::Core::RakeTask.new(:spec)
|
|
11
7
|
|
|
12
|
-
task default: :
|
|
8
|
+
task default: :spec
|