sshkeeper/sshkeeper_tz.md

28 KiB
Raw Blame History

ТЗ для ИИ-кодера: 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:

  • logsjournalctl -xe;
  • dockerdocker ps;
  • nginx-testnginx -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>

Логика:

  1. Найти профиль сервера в SQLite.
  2. Если нужен vault — запросить master password, если vault ещё не разблокирован.
  3. Сформировать команду ssh.
  4. Запустить системный /usr/bin/ssh.
  5. Если используется пароль — запустить SSH через PTY-wrapper.
  6. Дождаться password prompt.
  7. Отправить пароль в PTY.
  8. Передать управление пользователю.
  9. После завершения сессии обновить 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 сохраняет.
Пользователь сам решает, что ему нужно.