28 KiB
ТЗ для ИИ-кодера: sshkeeper — консольный менеджер SSH-подключений для Linux
1. Назначение проекта
Нужно разработать консольное приложение для Linux, которое работает как менеджер SSH-серверов и учётных данных.
Приложение не должно реализовывать собственный SSH-клиент с нуля. Оно должно использовать системный OpenSSH (/usr/bin/ssh) как реальный транспорт подключения, а само приложение должно быть удобным слоем управления над:
- списком серверов;
- пользователями;
- портами;
- SSH-ключами;
- SSH-паролями;
- группами;
- тегами;
- заметками;
- bastion/proxyjump;
- локальными настройками подключения;
- encrypted vault для секретов.
Рабочее название приложения: sshkeeper.
Главная идея:
sshkeeperне заменяет OpenSSH.
sshkeeperуправляет профилями подключений, секретами и удобным запуском SSH-сессий.
2. Целевая платформа
Основная целевая платформа:
- Linux x86_64;
- Arch Linux;
- Debian/Ubuntu;
- Fedora-compatible дистрибутивы.
На первом этапе Windows и macOS не обязательны.
3. Основной стек
Использовать:
- Go;
- Cobra для CLI;
- Bubble Tea для TUI;
- Bubbles для TUI-компонентов;
- SQLite для локальной базы профилей;
modernc.org/sqliteкак SQLite-драйвер без CGO;golang.org/x/crypto/argon2для Argon2id KDF;golang.org/x/crypto/chacha20poly1305для XChaCha20-Poly1305;github.com/creack/ptyдля запуска OpenSSH через PTY;- системный
/usr/bin/ssh.
Не использовать:
- Electron;
- GUI;
- внешние password managers как обязательную зависимость;
- GNOME Keyring/KWallet/libsecret как обязательную зависимость;
- хранение паролей в plaintext;
- передачу пароля через аргументы командной строки;
- передачу пароля через environment variables.
4. Основные пользовательские сценарии
4.1. Добавить сервер
Пользователь запускает:
sshkeeper add
Приложение открывает интерактивную форму в терминале.
Поля:
- Alias;
- Display name;
- Host;
- Port;
- User;
- Auth method:
password;key;key+passphrase;agent;
- Identity file, если используется ключ;
- Password, если используется пароль;
- Key passphrase, если используется ключ с passphrase;
- Group;
- Tags;
- Notes;
- ProxyJump, опционально;
- Local forwards, опционально;
- Remote forwards, опционально.
В конце формы должны быть две основные кнопки:
[Test] [Save]
4.2. Кнопка Test
Кнопка Test проверяет текущие введённые данные.
Важно:
Testне сохраняет профиль.Testне сохраняет новые секреты в постоянный vault.Testиспользует введённые данные только из текущей формы.- Если подключение успешно — показать
Connection OK. - Если подключение неуспешно — показать ошибку.
- После теста пользователь остаётся в форме.
- Все введённые значения должны сохраниться в форме.
- Пользователь сам решает, нажимать ли потом
Save.
Пример успешного теста:
Connection OK.
Пример ошибки пароля:
Connection failed:
Permission denied, please try again.
Пример ошибки ключа:
Connection failed:
Identity file ~/.ssh/prod_ed25519 not found.
Пример сетевой ошибки:
Connection failed:
connect to host 10.0.0.11 port 22: No route to host.
4.3. Кнопка Save
Кнопка Save сохраняет профиль как есть.
Важно:
Saveне обязан выполнять тест подключения.Saveне должен блокировать сохранение, если сервер недоступен.Saveне должен показывать раздражающие предупреждения про опасность SSH-паролей.- Если пользователь выбрал
password, пароль сохраняется в собственный encrypted vault. - Если пользователь выбрал
key+passphrase, passphrase сохраняется в encrypted vault. - Обычные данные профиля сохраняются в SQLite.
- Секреты не должны попадать в SQLite в открытом виде.
После сохранения показать:
Saved.
5. Пароли как штатный режим
Парольная авторизация должна быть полноценным штатным режимом.
Не нужно делать предупреждения вида:
WARNING! Password auth is insecure!
Такие предупреждения не нужны.
Логика:
auth=password— нормальный режим.- Пароль хранится в собственном encrypted vault.
- Пароль не хранится в YAML/TOML/SQLite открытым текстом.
- Пароль не передаётся в argv.
- Пароль не передаётся через env.
- Пароль не пишется в логи.
- Пароль не показывается в интерфейсе после ввода.
- В списке серверов можно показывать тип авторизации:
password,key,agent,key+passphrase.
6. Структура хранения данных
Использовать XDG-совместимые пути.
База данных:
~/.local/share/sshkeeper/sshkeeper.db
Vault:
~/.local/share/sshkeeper/vault.bin
Конфиг приложения:
~/.config/sshkeeper/config.toml
Сгенерированный OpenSSH config:
~/.ssh/config.d/sshkeeper.conf
Пользовательский ~/.ssh/config может содержать:
Include ~/.ssh/config.d/*.conf
Если такой строки нет, приложение должно уметь добавить её отдельной командой:
sshkeeper ssh-config install-include
Делать это автоматически без явного действия пользователя не нужно.
7. SQLite-схема MVP
Минимальные таблицы:
7.1. servers
Поля:
id;alias;display_name;host;port;user;auth_method;identity_file;proxy_jump;group_name;notes;created_at;updated_at;last_connected_at;last_test_at;last_test_status;last_test_error.
auth_method значения:
password;key;key_passphrase;agent.
last_test_status значения:
unknown;ok;failed.
7.2. tags
Поля:
id;name.
7.3. server_tags
Поля:
server_id;tag_id.
7.4. forwards
Поля:
id;server_id;type;local_addr;local_port;remote_addr;remote_port.
type значения:
local;remote;dynamic.
7.5. command_templates
Поля:
id;server_id;name;command.
Примеры command templates:
logs→journalctl -xe;docker→docker ps;nginx-test→nginx -t.
8. Vault
Нужен собственный encrypted vault.
8.1. Master password
При первом запуске:
sshkeeper init
Приложение спрашивает:
Create master password:
Repeat master password:
Master password не хранится.
Из master password через Argon2id выводится master key.
8.2. KDF
Использовать Argon2id.
Начальные параметры:
memory: 64 MiB
iterations: 3
parallelism: 1
salt: random 16 или 32 bytes
key length: 32 bytes
Параметры KDF должны храниться в metadata vault, чтобы в будущем можно было менять настройки.
8.3. Шифрование
Использовать XChaCha20-Poly1305.
Каждая запись vault должна иметь отдельный случайный nonce.
Формат vault можно сделать JSON или бинарный. Для MVP допустим JSON с base64-полями.
Пример структуры:
{
"version": 1,
"kdf": {
"name": "argon2id",
"memory_kib": 65536,
"iterations": 3,
"parallelism": 1,
"salt": "base64..."
},
"records": [
{
"id": "server:old-router:ssh-password",
"type": "ssh_password",
"nonce": "base64...",
"ciphertext": "base64..."
}
]
}
8.4. Типы секретов
Поддержать типы:
ssh_password;key_passphrase;sudo_password;custom_secret.
Для MVP обязательны:
ssh_password;key_passphrase.
8.5. Secret references
В SQLite хранить только ссылки на секреты.
Примеры:
server:old-router:ssh-password
server:prod-web-1:key-passphrase
server:prod-web-1:sudo-password
Секреты должны лежать только в vault.
9. Подключение к серверу
Команда:
sshkeeper connect <alias>
Короткий алиас:
sshkeeper c <alias>
Логика:
- Найти профиль сервера в SQLite.
- Если нужен vault — запросить master password, если vault ещё не разблокирован.
- Сформировать команду
ssh. - Запустить системный
/usr/bin/ssh. - Если используется пароль — запустить SSH через PTY-wrapper.
- Дождаться password prompt.
- Отправить пароль в PTY.
- Передать управление пользователю.
- После завершения сессии обновить
last_connected_at.
10. PTY-wrapper для паролей
Для auth=password нельзя передавать пароль через аргументы командной строки.
Нужен PTY-wrapper.
Примерная логика:
start ssh through PTY
read PTY output
detect password prompt
write password + "\n"
after login, bridge stdin/stdout/stderr between user terminal and PTY
handle terminal resize
restore terminal state on exit
Prompt detection должен учитывать варианты:
password:
Password:
user@host's password:
Enter password:
Для MVP достаточно английских вариантов.
Позже можно добавить расширяемые regex-шаблоны в config.
11. Проверка подключения
Команда:
sshkeeper test <alias>
В форме сервера кнопка:
[Test]
Проверка должна выполнять короткую безопасную команду:
echo SSHKEEPER_OK
Ожидаемый вывод:
SSHKEEPER_OK
Если команда выполнилась и вывод получен — тест успешен.
Для нестандартных систем можно добавить fallback:
exit
Но для MVP достаточно echo SSHKEEPER_OK.
Важно:
- Тест из формы не сохраняет профиль.
- Тест существующего профиля обновляет:
last_test_at;last_test_status;last_test_error.
- Тест должен иметь timeout.
- Начальный timeout: 10 секунд.
- Timeout должен настраиваться в config.
12. CLI-команды MVP
Обязательные команды:
sshkeeper init
sshkeeper add
sshkeeper list
sshkeeper show <alias>
sshkeeper edit <alias>
sshkeeper delete <alias>
sshkeeper connect <alias>
sshkeeper c <alias>
sshkeeper test <alias>
sshkeeper search <query>
sshkeeper vault lock
sshkeeper vault unlock
sshkeeper vault status
sshkeeper vault change-password
sshkeeper config path
sshkeeper ssh-config generate
sshkeeper ssh-config install-include
Дополнительные команды, если останется время:
sshkeeper import ~/.ssh/config
sshkeeper export
sshkeeper run <alias> <command>
sshkeeper run-template <alias> <template>
sshkeeper group list
sshkeeper group test <group>
13. TUI MVP
При запуске без аргументов:
sshkeeper
Открывается TUI.
Главный экран:
┌─ sshkeeper ─────────────────────────────────────────────┐
│ Search: _ │
├──────────────────────────────────────────────────────────┤
│ [?] home-nextcloud admin@192.168.1.15:22 key │
│ [✓] prod-web-1 root@10.0.0.11:22 key │
│ [!] old-router admin@192.168.1.1:22 password │
└──────────────────────────────────────────────────────────┘
Enter connect | a add | e edit | d delete | t test | / search | q quit
Статусы:
[?] never tested
[✓] last test OK
[!] last test failed
Клавиши:
Enter— connect;a— add;e— edit;d— delete;t— test;/— search;q— quit;Esc— назад/отмена.
Форма добавления/редактирования должна иметь две основные кнопки:
[Test] [Save]
Test проверяет, но не сохраняет.
Save сохраняет без обязательного теста.
14. Генерация OpenSSH config
Команда:
sshkeeper ssh-config generate
Должна создавать файл:
~/.ssh/config.d/sshkeeper.conf
Для серверов с ключами можно генерировать:
Host prod-web-1
HostName 10.0.0.11
User root
Port 22
IdentityFile ~/.ssh/prod_ed25519
ProxyJump bastion
Для password-auth профилей тоже можно генерировать базовый host без пароля:
Host old-router
HostName 192.168.1.1
User admin
Port 22
Пароли в ssh config не писать никогда.
15. Логи
Логи должны быть аккуратными.
Не логировать:
- SSH-пароли;
- key passphrase;
- master password;
- decrypted vault content.
Можно логировать:
- alias;
- host;
- port;
- user;
- auth method;
- ошибку подключения без секретов.
Логи MVP можно писать только в stderr/debug mode.
16. Безопасность файлов
При создании файлов выставлять права:
~/.local/share/sshkeeper/sshkeeper.db 0600
~/.local/share/sshkeeper/vault.bin 0600
~/.config/sshkeeper/config.toml 0600
~/.ssh/config.d/sshkeeper.conf 0600
Директории:
~/.local/share/sshkeeper 0700
~/.config/sshkeeper 0700
~/.ssh/config.d 0700 или существующие безопасные права
17. Конфиг приложения
Файл:
~/.config/sshkeeper/config.toml
Пример:
[ssh]
binary = "/usr/bin/ssh"
connect_timeout_seconds = 10
test_command = "echo SSHKEEPER_OK"
[vault]
auto_lock_minutes = 15
[ui]
show_security_hints = false
18. Архитектура кода
Предлагаемая структура проекта:
sshkeeper/
go.mod
go.sum
main.go
cmd/
root.go
init.go
add.go
list.go
show.go
edit.go
delete.go
connect.go
test.go
search.go
vault.go
ssh_config.go
internal/
app/
app.go
config/
config.go
paths.go
db/
db.go
migrations.go
servers.go
tags.go
forwards.go
model/
server.go
secret.go
forward.go
tag.go
vault/
vault.go
crypto.go
format.go
unlock.go
ssh/
command.go
launcher.go
pty.go
test.go
configgen.go
tui/
app.go
list.go
form.go
styles.go
prompt/
input.go
password.go
util/
fs.go
time.go
errors.go
19. Ошибки и UX
Ошибки должны быть понятными человеку.
Плохо:
exit status 255
Хорошо:
Connection failed:
Permission denied.
Server: old-router
Target: admin@192.168.1.1:22
Auth: password
При этом не надо добавлять длинные лекции и предупреждения.
20. Этапы разработки
Этап 1. Каркас CLI
Сделать:
- Go module;
- Cobra root command;
- команды:
init;add;list;show;delete;
- XDG paths;
- создание директорий;
- базовый config.
Критерий готовности:
sshkeeper init
sshkeeper add
sshkeeper list
sshkeeper show test-server
Команды работают.
Этап 2. SQLite
Сделать:
- SQLite подключение;
- migrations;
- таблицу
servers; - CRUD серверов;
- хранение tags/groups/notes можно пока упростить.
Критерий готовности:
- серверы сохраняются между запусками;
- можно добавить, посмотреть, удалить сервер.
Этап 3. Vault
Сделать:
vault.bin;- master password;
- Argon2id key derivation;
- XChaCha20-Poly1305 encryption;
- команды:
vault unlock;vault lock;vault status;vault change-password;
- сохранение
ssh_password; - сохранение
key_passphrase.
Критерий готовности:
- пароль можно сохранить;
- пароль не виден в SQLite;
- после перезапуска пароль можно достать только через master password.
Этап 4. Connect через OpenSSH
Сделать:
sshkeeper connect <alias>;sshkeeper c <alias>;- запуск
/usr/bin/ssh; - key auth;
- agent auth;
- password auth через PTY-wrapper.
Критерий готовности:
- можно подключиться к серверу по ключу;
- можно подключиться к серверу по паролю;
- пароль не передаётся через argv/env.
Этап 5. Test
Сделать:
sshkeeper test <alias>;- test command:
echo SSHKEEPER_OK; - timeout;
- обновление:
last_test_at;last_test_status;last_test_error.
Критерий готовности:
- успешное подключение отмечается
[✓]; - неуспешное —
[!]; - ошибка сохраняется и показывается.
Этап 6. TUI
Сделать:
- запуск TUI при
sshkeeper; - список серверов;
- поиск;
- connect;
- add form;
- edit form;
- delete;
- test;
- кнопки
[Test] [Save].
Критерий готовности:
- приложением можно пользоваться без знания CLI-команд;
- добавление/редактирование сервера возможно из TUI;
Testне сохраняет;Saveсохраняет без обязательного теста.
Этап 7. OpenSSH config generation
Сделать:
sshkeeper ssh-config generate;- генерация
~/.ssh/config.d/sshkeeper.conf; sshkeeper ssh-config install-include;- не писать пароли в ssh config.
Критерий готовности:
- после генерации можно выполнить:
ssh alias
для key/agent/password профилей, где password всё равно будет спрашиваться самим ssh.
Этап 8. Полировка
Сделать:
- импорт из
~/.ssh/config; - export/backup;
- groups;
- command templates;
- run command;
- group test;
- fuzzy search;
- нормальные help-сообщения.
21. Минимальный acceptance checklist
Приложение считается MVP-готовым, если:
sshkeeper initсоздаёт конфиг, БД и vault.sshkeeper addдобавляет сервер.sshkeeper listпоказывает серверы.sshkeeper show <alias>показывает профиль без секретов.sshkeeper edit <alias>редактирует профиль.sshkeeper delete <alias>удаляет профиль.sshkeeper c <alias>подключается через OpenSSH.- password-auth работает через PTY.
- key-auth работает.
- key+passphrase работает.
- пароль не хранится в plaintext.
- пароль не передаётся через argv/env.
sshkeeper test <alias>проверяет подключение.- TUI запускается командой
sshkeeper. - В TUI есть список серверов.
- В TUI есть форма add/edit.
- В форме есть кнопки
TestиSave. Testне сохраняет.Saveсохраняет без обязательного теста.sshkeeper ssh-config generateсоздаёт OpenSSH config без паролей.
Инструкция по настройке среды разработки, сборке и запуску
1. Установить зависимости ОС
Arch Linux
sudo pacman -Syu
sudo pacman -S go git openssh make
Debian/Ubuntu
sudo apt update
sudo apt install -y golang-go git openssh-client make
Если нужна свежая версия Go, лучше установить Go с официального сайта, а не из репозитория дистрибутива.
Проверить:
go version
git --version
ssh -V
2. Создать проект
mkdir -p ~/projects
cd ~/projects
mkdir sshkeeper
cd sshkeeper
go mod init github.com/mirivlad/sshkeeper
3. Установить зависимости Go
go get github.com/spf13/cobra@latest
go get github.com/charmbracelet/bubbletea@latest
go get github.com/charmbracelet/bubbles@latest
go get github.com/charmbracelet/lipgloss@latest
go get modernc.org/sqlite@latest
go get golang.org/x/crypto@latest
go get github.com/creack/pty@latest
4. Создать минимальный main.go
package main
import "github.com/mirivlad/sshkeeper/cmd"
func main() {
cmd.Execute()
}
5. Создать Makefile
APP=sshkeeper
.PHONY: build run test clean install
build:
go build -o bin/$(APP) .
run:
go run .
test:
go test ./...
clean:
rm -rf bin
install:
go build -o $(HOME)/.local/bin/$(APP) .
6. Собрать приложение
make build
Проверить:
./bin/sshkeeper --help
7. Запустить из исходников
go run . --help
go run . init
go run . list
8. Установить локально в PATH
Убедиться, что есть директория:
mkdir -p ~/.local/bin
Добавить в ~/.bashrc или ~/.zshrc, если ещё не добавлено:
export PATH="$HOME/.local/bin:$PATH"
Применить:
source ~/.bashrc
или:
source ~/.zshrc
Установить:
make install
Проверить:
sshkeeper --help
9. Первый запуск
sshkeeper init
Ожидаемый результат:
Created config: ~/.config/sshkeeper/config.toml
Created database: ~/.local/share/sshkeeper/sshkeeper.db
Created vault: ~/.local/share/sshkeeper/vault.bin
10. Добавить тестовый сервер
Интерактивно:
sshkeeper add
Или позже можно реализовать неинтерактивный вариант:
sshkeeper add test-vps \
--host 192.168.1.10 \
--port 22 \
--user root \
--auth key \
--identity-file ~/.ssh/id_ed25519
11. Проверить подключение
sshkeeper test test-vps
12. Подключиться
sshkeeper c test-vps
13. Запустить TUI
sshkeeper
14. Сгенерировать OpenSSH config
sshkeeper ssh-config generate
При необходимости добавить Include:
sshkeeper ssh-config install-include
После этого для части профилей можно будет подключаться напрямую:
ssh test-vps
15. Рекомендации к разработке
После каждого этапа запускать:
go fmt ./...
go vet ./...
go test ./...
make build
Если есть ошибки — исправить до перехода к следующему этапу.
Не переходить к TUI, пока CLI, SQLite, vault и connect не работают стабильно.
Примечания по философии проекта
sshkeeper должен оставаться слоем управления, а не пытаться стать новым OpenSSH.
OpenSSH уже умеет:
Host;HostName;User;Port;IdentityFile;ProxyJump;LocalForward;RemoteForward;Include.
Поэтому sshkeeper должен:
- использовать OpenSSH как транспорт;
- генерировать совместимый config;
- хранить секреты отдельно;
- давать удобный CLI/TUI;
- не спорить с пользователем;
- помогать, но не блокировать.
Главное UX-правило:
Testпроверяет.
Saveсохраняет.
Пользователь сам решает, что ему нужно.