v1.8.10: security audit fixes
- --list and --status no longer expose IP/port/user (only aliases) - --list-full for admin use (not in skill) - Removed --add from /ssh skill (servers added via GUI only) - Removed exact file paths from skill template - Added deny-read rules for ~/.server-connections/ files - Wrapped main() in try/except to prevent traceback leaking - Added needs_reencrypt() to encryption.py for future migration - install_key no longer prints server IP Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -38,3 +38,17 @@ def is_encrypted(data: bytes) -> bool:
|
|||||||
return data.decode("utf-8").strip().startswith("gAAAAA")
|
return data.decode("utf-8").strip().startswith("gAAAAA")
|
||||||
except UnicodeDecodeError:
|
except UnicodeDecodeError:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def needs_reencrypt(data: bytes) -> bool:
|
||||||
|
"""Check if data was encrypted with old key and needs re-encryption."""
|
||||||
|
try:
|
||||||
|
_fernet.decrypt(data)
|
||||||
|
return False
|
||||||
|
except InvalidToken:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
_fernet_old.decrypt(data)
|
||||||
|
return True
|
||||||
|
except InvalidToken:
|
||||||
|
return False
|
||||||
|
|||||||
BIN
releases/ServerManager-v1.8.10-win-x64.exe
Normal file
BIN
releases/ServerManager-v1.8.10-win-x64.exe
Normal file
Binary file not shown.
@@ -4,12 +4,14 @@
|
|||||||
|
|
||||||
## ВАЖНО — Безопасность
|
## ВАЖНО — Безопасность
|
||||||
|
|
||||||
- **НИКОГДА не читай** `D:\CODING\GitHub\.server-connections\servers.json` — там пароли
|
- **НИКОГДА не читай файлы** в директории `~/.server-connections/` напрямую
|
||||||
|
- **НИКОГДА не читай** файлы `encryption.py`, `servers.json`, `settings.json`
|
||||||
- **НИКОГДА не выводи пароли** пользователю
|
- **НИКОГДА не выводи пароли** пользователю
|
||||||
- **Все операции только через** `python /d/CODING/GitHub/.server-connections/ssh.py`
|
- **Все операции только через** `python /d/CODING/GitHub/.server-connections/ssh.py`
|
||||||
- Скрипт сам читает credentials, подключается, выполняет, возвращает результат
|
- Скрипт сам читает credentials, подключается, выполняет, возвращает результат
|
||||||
- **МАКСИМУМ 1 попытка** подключения. Если timeout/ошибка — сообщи, НЕ повторяй
|
- **МАКСИМУМ 1 попытка** подключения. Если timeout/ошибка — сообщи, НЕ повторяй
|
||||||
- fail2ban банит IP после 5-10 неудач — спам попытками УБЬЁТ доступ к серверу
|
- fail2ban банит IP после 5-10 неудач — спам попытками УБЬЁТ доступ к серверу
|
||||||
|
- **Серверы добавляются ТОЛЬКО через GUI** ServerManager, НЕ через CLI
|
||||||
|
|
||||||
## Аргументы
|
## Аргументы
|
||||||
|
|
||||||
@@ -43,22 +45,16 @@ python /d/CODING/GitHub/.server-connections/ssh.py ALIAS --install-key
|
|||||||
python /d/CODING/GitHub/.server-connections/ssh.py ALIAS --ping
|
python /d/CODING/GitHub/.server-connections/ssh.py ALIAS --ping
|
||||||
```
|
```
|
||||||
|
|
||||||
### Список серверов (без паролей)
|
### Список серверов (только алиасы, без IP/паролей)
|
||||||
```bash
|
```bash
|
||||||
python /d/CODING/GitHub/.server-connections/ssh.py --list
|
python /d/CODING/GitHub/.server-connections/ssh.py --list
|
||||||
```
|
```
|
||||||
|
|
||||||
### Статус всех серверов
|
### Статус всех серверов (только алиасы + online/offline)
|
||||||
```bash
|
```bash
|
||||||
python /d/CODING/GitHub/.server-connections/ssh.py --status
|
python /d/CODING/GitHub/.server-connections/ssh.py --status
|
||||||
```
|
```
|
||||||
|
|
||||||
### Добавить новый сервер
|
|
||||||
```bash
|
|
||||||
python /d/CODING/GitHub/.server-connections/ssh.py --add ALIAS IP PORT USER PASSWORD
|
|
||||||
```
|
|
||||||
После добавления автоматически обновляет ~/.ssh/config и устанавливает SSH-ключ.
|
|
||||||
|
|
||||||
### Удалить сервер
|
### Удалить сервер
|
||||||
```bash
|
```bash
|
||||||
python /d/CODING/GitHub/.server-connections/ssh.py --remove ALIAS
|
python /d/CODING/GitHub/.server-connections/ssh.py --remove ALIAS
|
||||||
|
|||||||
39
tools/ssh.py
39
tools/ssh.py
@@ -209,7 +209,7 @@ def install_key(server: dict):
|
|||||||
)
|
)
|
||||||
out, err, code = run_command(server, command, use_sudo=False)
|
out, err, code = run_command(server, command, use_sudo=False)
|
||||||
if "KEY_OK" in out:
|
if "KEY_OK" in out:
|
||||||
print(f"SSH key installed on {server['alias']} ({server['ip']})")
|
print(f"SSH key installed on {server['alias']}")
|
||||||
else:
|
else:
|
||||||
print(f"ERROR: {err or out}")
|
print(f"ERROR: {err or out}")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
@@ -226,19 +226,28 @@ def ping_server(server: dict):
|
|||||||
print(f"{server['alias']}: OFFLINE ({type(e).__name__})")
|
print(f"{server['alias']}: OFFLINE ({type(e).__name__})")
|
||||||
|
|
||||||
|
|
||||||
def list_servers():
|
def list_servers(full=False):
|
||||||
_, servers = load_servers()
|
_, servers = load_servers()
|
||||||
print(f"{'Alias':<20} {'IP':<20} {'Port':<8} {'User':<10} {'Key':<6}")
|
if full:
|
||||||
print("-" * 64)
|
print(f"{'Alias':<20} {'IP':<20} {'Port':<8} {'User':<10} {'Key':<6}")
|
||||||
for alias, s in servers.items():
|
print("-" * 64)
|
||||||
has_key = "yes" if os.path.exists(SSH_KEY_PATH) else "no"
|
for alias, s in servers.items():
|
||||||
print(f"{alias:<20} {s['ip']:<20} {s.get('port', 22):<8} {s.get('user', 'root'):<10} {has_key:<6}")
|
has_key = "yes" if os.path.exists(SSH_KEY_PATH) else "no"
|
||||||
|
print(f"{alias:<20} {s['ip']:<20} {s.get('port', 22):<8} {s.get('user', 'root'):<10} {has_key:<6}")
|
||||||
|
else:
|
||||||
|
# Safe mode: only aliases (no IPs, ports, users)
|
||||||
|
print(f"{'Alias':<20} {'Type':<10} {'Key':<6}")
|
||||||
|
print("-" * 36)
|
||||||
|
for alias, s in servers.items():
|
||||||
|
has_key = "yes" if os.path.exists(SSH_KEY_PATH) else "no"
|
||||||
|
stype = s.get("type", "ssh")
|
||||||
|
print(f"{alias:<20} {stype:<10} {has_key:<6}")
|
||||||
|
|
||||||
|
|
||||||
def check_status():
|
def check_status():
|
||||||
_, servers = load_servers()
|
_, servers = load_servers()
|
||||||
print(f"{'Alias':<20} {'IP':<20} {'Status':<10}")
|
print(f"{'Alias':<20} {'Status':<10}")
|
||||||
print("-" * 50)
|
print("-" * 30)
|
||||||
for alias, s in servers.items():
|
for alias, s in servers.items():
|
||||||
try:
|
try:
|
||||||
client = get_client(s)
|
client = get_client(s)
|
||||||
@@ -246,7 +255,7 @@ def check_status():
|
|||||||
status = "ONLINE"
|
status = "ONLINE"
|
||||||
except Exception:
|
except Exception:
|
||||||
status = "OFFLINE"
|
status = "OFFLINE"
|
||||||
print(f"{alias:<20} {s['ip']:<20} {status:<10}")
|
print(f"{alias:<20} {status:<10}")
|
||||||
|
|
||||||
|
|
||||||
def add_server(args):
|
def add_server(args):
|
||||||
@@ -335,6 +344,8 @@ def main():
|
|||||||
|
|
||||||
if cmd == "--list":
|
if cmd == "--list":
|
||||||
list_servers(); sys.exit(0)
|
list_servers(); sys.exit(0)
|
||||||
|
if cmd == "--list-full":
|
||||||
|
list_servers(full=True); sys.exit(0)
|
||||||
if cmd == "--status":
|
if cmd == "--status":
|
||||||
check_status(); sys.exit(0)
|
check_status(); sys.exit(0)
|
||||||
if cmd == "--add":
|
if cmd == "--add":
|
||||||
@@ -379,4 +390,10 @@ def main():
|
|||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
try:
|
||||||
|
main()
|
||||||
|
except SystemExit:
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
print(f"ERROR: {type(e).__name__}: {e}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""Version info for ServerManager."""
|
"""Version info for ServerManager."""
|
||||||
|
|
||||||
__version__ = "1.8.8"
|
__version__ = "1.8.10"
|
||||||
__app_name__ = "ServerManager"
|
__app_name__ = "ServerManager"
|
||||||
__author__ = "aibot777"
|
__author__ = "aibot777"
|
||||||
__description__ = "Desktop GUI for managing remote servers"
|
__description__ = "Desktop GUI for managing remote servers"
|
||||||
|
|||||||
Reference in New Issue
Block a user