230 lines
8.3 KiB
Markdown
230 lines
8.3 KiB
Markdown
# Аудит Redis — полный отчёт
|
||
|
||
**Дата:** 2026-02-28
|
||
**Версия:** v1.8.65
|
||
**Тестовый сервер:** Reddis main ovh (Redis 8.2.1, 27M+ ключей, 2.82GB RAM)
|
||
|
||
---
|
||
|
||
## 1. Живое тестирование через CLI
|
||
|
||
### Работает корректно
|
||
|
||
| Команда | Результат |
|
||
|---------|-----------|
|
||
| `--redis-info ALIAS` | Версия, память, клиенты, keyspace |
|
||
| `--redis-keys ALIAS "*"` | SCAN до 1000 ключей, сортировка |
|
||
| `--redis ALIAS "PING"` | `True` |
|
||
| `--redis ALIAS "DBSIZE"` | `27199166` |
|
||
| `--redis ALIAS "GET key"` | Возвращает значение |
|
||
| `--redis ALIAS "SET key value"` | `True` |
|
||
| `--redis ALIAS "DEL key1 key2"` | Число удалённых |
|
||
| `--redis ALIAS "KEYS pattern"` | Список с нумерацией |
|
||
| `--redis ALIAS "INFO memory"` | Полный вывод секции |
|
||
| `--redis ALIAS "TYPE key"` | Тип данных |
|
||
| `--redis ALIAS "TTL key"` | Секунды / -1 |
|
||
| `--redis ALIAS "SELECT 1"` | `True` |
|
||
| `--redis ALIAS "MSET k1 v1 k2 v2"` | `True` |
|
||
| `--status` (redis) | `ONLINE` |
|
||
|
||
### Сломано
|
||
|
||
| Команда | Ошибка | Баг |
|
||
|---------|--------|-----|
|
||
| `SET key "hello world"` | `syntax error` | #1 |
|
||
| `SET key '{"a":1, "b":2}'` | `syntax error` | #1 |
|
||
| Любое значение с пробелами | `syntax error` | #1 |
|
||
|
||
---
|
||
|
||
## 2. Найденные баги
|
||
|
||
### БАГ #1 — КРИТИЧНЫЙ: Парсер команд ломает значения с пробелами
|
||
|
||
**Файлы:** `tools/ssh.py:958`, `core/redis_client.py:78`
|
||
|
||
**Проблема:** Используется `command.split()` вместо `shlex.split()`.
|
||
|
||
```python
|
||
# Текущий код:
|
||
parts = command.split()
|
||
# "SET key 'hello world'" → ['SET', 'key', "'hello", "world'"]
|
||
# Redis получает 4 аргумента вместо 3 → syntax error
|
||
```
|
||
|
||
**Воздействие:**
|
||
- Невозможно SET/GET значения с пробелами
|
||
- Невозможно работать с JSON-данными содержащими пробелы
|
||
- Невозможно выполнить EVAL с Lua-скриптами
|
||
- Сломаны команды с multi-word аргументами (SORT, CONFIG SET и др.)
|
||
|
||
**Затронуты:** CLI (`tools/ssh.py`) И GUI (`core/redis_client.py` → `redis_tab.py`)
|
||
|
||
---
|
||
|
||
### БАГ #2 — СРЕДНИЙ: GUI stats показывает отформатированную строку
|
||
|
||
**Файл:** `gui/tabs/redis_tab.py:192-208`
|
||
|
||
**Проблема:** `_refresh_stats()` вызывает `client.execute("DBSIZE")`, который возвращает строку `"(integer) 27199166"` (уже отформатированную через `_format()`). Эта строка отображается как есть.
|
||
|
||
```python
|
||
# redis_tab.py:198
|
||
keys_count = client.execute("DBSIZE") # Returns "(integer) 27199166"
|
||
keys_text = str(keys_count) # → "(integer) 27199166" in label
|
||
```
|
||
|
||
**Для INFO memory проблема другая:**
|
||
```python
|
||
# redis_tab.py:201
|
||
info = client.execute("INFO memory")
|
||
# _format() превращает dict в строку, но без \r\n
|
||
# Поиск по \r\n ничего не найдёт → memory = "—" всегда
|
||
for line in info.split("\r\n"): # \r\n не найдётся!
|
||
if line.startswith("used_memory_human:"):
|
||
memory = line.split(":")[1].strip()
|
||
```
|
||
|
||
**Воздействие:** Статистика в GUI:
|
||
- Keys показывает `(integer) 27199166` вместо `27199166`
|
||
- Memory всегда показывает `—` (парсинг не находит `\r\n`)
|
||
|
||
---
|
||
|
||
### БАГ #3 — СРЕДНИЙ: GUI RedisClient не поддерживает SSL
|
||
|
||
**Файл:** `core/redis_client.py:35-43`
|
||
|
||
**Проблема:** В конструкторе `redis.Redis()` отсутствует параметр `ssl`.
|
||
|
||
```python
|
||
# redis_client.py:35-43
|
||
self._conn = r.Redis(
|
||
host=self._host,
|
||
port=self._port,
|
||
password=self._password,
|
||
db=self._db,
|
||
decode_responses=True,
|
||
socket_timeout=5,
|
||
socket_connect_timeout=5,
|
||
# ❌ ОТСУТСТВУЕТ: ssl=...
|
||
)
|
||
```
|
||
|
||
При этом CLI (`tools/ssh.py:955-956`) SSL поддерживает:
|
||
```python
|
||
ssl_enabled = server.get("ssl", False)
|
||
r = redis_lib.Redis(..., ssl=ssl_enabled)
|
||
```
|
||
|
||
**Воздействие:** GUI не может подключиться к Redis с TLS/SSL.
|
||
|
||
**Связанная проблема:** В `server_dialog.py` поле `use_ssl`/`ssl` НЕ включено в `FIELD_MAP["redis"]`:
|
||
```python
|
||
"redis": ["password", "db_index"] # нет ssl!
|
||
```
|
||
|
||
Даже если добавить SSL в RedisClient, пользователь не сможет включить его в UI.
|
||
|
||
---
|
||
|
||
### БАГ #4 — НИЗКИЙ: CLI Redis ошибки выдают сырой traceback
|
||
|
||
**Файл:** `tools/ssh.py:957-962`
|
||
|
||
**Проблема:** `r.execute_command()` не обёрнут в try-except.
|
||
|
||
```python
|
||
# Текущий код:
|
||
result = r.execute_command(*parts) # При ошибке → необработанное исключение
|
||
```
|
||
|
||
**Пример:** `HGETALL` на string-ключе:
|
||
```
|
||
ERROR: ResponseError: WRONGTYPE Operation against a key holding the wrong kind of value
|
||
```
|
||
Выводится как traceback с двойным повтором вместо читаемого сообщения.
|
||
|
||
---
|
||
|
||
### БАГ #5 — НИЗКИЙ: Двойной PING при status check
|
||
|
||
**Файл:** `core/status_checker.py:98-110`
|
||
|
||
```python
|
||
def _check_redis(self, server: dict) -> bool:
|
||
client = RedisClient(server)
|
||
result = client.connect() # ← PING #1 внутри connect()
|
||
if result:
|
||
ok = client.check_connection() # ← PING #2
|
||
client.disconnect()
|
||
return ok
|
||
return False
|
||
```
|
||
|
||
`connect()` уже делает `self._conn.ping()` → `True`. Потом `check_connection()` вызывает `self._conn.ping()` ещё раз.
|
||
|
||
---
|
||
|
||
### БАГ #6 — НИЗКИЙ: CLI status для Redis без try-except
|
||
|
||
**Файл:** `tools/ssh.py:698-703`
|
||
|
||
```python
|
||
if stype == "redis":
|
||
r = redis_lib.Redis(...)
|
||
r.ping() # ← Нет try-except! Если offline → traceback
|
||
r.close()
|
||
return "ONLINE"
|
||
```
|
||
|
||
Сравни с SQL, где `conn.close()` в finally. Здесь если `ping()` упадёт, `r.close()` не вызовется.
|
||
|
||
---
|
||
|
||
### ПРОБЛЕМА #7 — Дублирование кода подключения
|
||
|
||
**Файлы:** `tools/ssh.py:946-956`, `tools/ssh.py:978-986`, `tools/ssh.py:1009-1017`
|
||
|
||
Три функции (`run_redis_cmd`, `redis_info`, `redis_keys`) создают одинаковое подключение к Redis копипастом:
|
||
|
||
```python
|
||
import redis as redis_lib
|
||
host = server["ip"]
|
||
port = server.get("port", 6379)
|
||
password = server.get("password", "") or None
|
||
db_index = server.get("db_index", 0)
|
||
ssl_enabled = server.get("ssl", False)
|
||
r = redis_lib.Redis(host=host, port=port, ...)
|
||
```
|
||
|
||
Этот блок повторяется 3 раза. Вынести в хелпер.
|
||
|
||
---
|
||
|
||
## 3. Карта проблем по файлам
|
||
|
||
| Файл | Строки | Баги |
|
||
|------|--------|------|
|
||
| `tools/ssh.py` | 958 | #1 (split), #4 (error handling), #6 (status), #7 (copypaste) |
|
||
| `core/redis_client.py` | 78, 35-43 | #1 (split), #3 (no SSL) |
|
||
| `gui/tabs/redis_tab.py` | 192-208 | #2 (stats parsing) |
|
||
| `core/status_checker.py` | 98-110 | #5 (double ping) |
|
||
| `gui/server_dialog.py` | 22 | #3 (no ssl field for redis) |
|
||
|
||
---
|
||
|
||
## 4. Влияние на пользователей
|
||
|
||
| Сценарий | Статус | Причина |
|
||
|----------|--------|---------|
|
||
| Простые команды (GET/SET/DEL) | ✅ Работает | Нет пробелов |
|
||
| Команды со значениями с пробелами | ❌ Сломано | Баг #1 |
|
||
| JSON-данные с пробелами | ❌ Сломано | Баг #1 |
|
||
| GUI статистика Keys | ⚠️ Некорректно | Баг #2 |
|
||
| GUI статистика Memory | ❌ Всегда "—" | Баг #2 |
|
||
| SSL-подключение через GUI | ❌ Невозможно | Баг #3 |
|
||
| SSL-подключение через CLI | ✅ Работает | — |
|
||
| Ошибки в CLI | ⚠️ Нечитаемые | Баг #4 |
|
||
| Status check | ✅ Работает (с оверхедом) | Баг #5 |
|