Skip to content

About

Client-server password manager in Go: secure storage and sync of logins, cards, text and binary data with a cross-platform CLI.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

1,102 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

GophKeeper

Клиент-серверный менеджер паролей на Go: логины, карты, текст и бинарные файлы. Секреты шифруются на клиенте, на сервере хранятся только ciphertext и метаданные. Управление — через CLI.

Архитектура

Два сервиса — cmd/server (HTTP API, опционально gRPC) и cmd/client (CLI). Написаны по Clean Architecture: presentation → application → domain → adapters.

Общая инфраструктура вынесена в app/internal/pkg, модули не дублируют инфраструктурный код.

Клиент — три модуля

Модуль Ответственность
identity регистрация, login/logout, session file (JWT access + refresh)
vault CRUD items, envelope-шифрование (MK/DEK), binary upload на сервер
sync локальный SQLite-кэш, offline read-write (pull/push, очередь мутаций, разрешение конфликтов)

Сервер — два модуля

Модуль Ответственность
identity пользователи, bcrypt, выдача и ротация JWT (access + refresh)
vault хранение items, multipart upload

Хранилище: Postgres + MinIO. TLS — в проде на HTTP и (при включённом gRPC) на gRPC-сервере — те же cert_file / key_file.

Межмодульное взаимодействие

Внутри одного сервиса модули не импортируют use case соседа напрямую. Схема:

  • у потребителя — port в application;
  • у поставщика — use cases application;
  • связка — intermodule-адаптер в adapters/client/intermodule, который вызывает use case другого модуля.

Так модули остаются изолированными срезами. При переходе на микросервисы достаточно взять pkg + код модуля и заменить intermodule-адаптер на вызов настоящего HTTP/gRPC API соседнего сервиса.

Функциональность

Сервер: регистрация и аутентификация (bcrypt, JWT access + refresh, logout с отзывом refresh); HTTP REST для identity и vault (всегда); опциональный gRPC для тех же модулей; хранение ciphertext и plaintext metadata в Postgres; крупные binary — в MinIO (single upload и multipart по частям, presigned URL, GC просроченных загрузок); оптимистичная блокировка через revision / If-Match; опциональный TLS в prod.

Клиент: CLI (one-shot и REPL session); транспорт к API сервера — protocol: http (по умолчанию) или protocol: grpc; типы items — credential, card, text, binary; envelope на клиенте (MK + DEK, Argon2id, AES-GCM) — сервер не видит plaintext секретов; ротация DEK (rotate-dek) и смена master password с пересохранением wrapped_dek (rewrap-master-password); локальный SQLite-кэш; sync pull с сервера; offline read-write: list/get из кэша, create/update/delete в очередь → sync push; разрешение конфликтов (sync resolve: local / server / lww); resume прерванного multipart upload; session file с авто-refresh access token на vault-командах.

Сквозной сценарий: один пользователь — несколько устройств; данные синхронизируются через vault API + клиентский sync.


Что понадобится

Компонент Назначение
Go 1.24+ сборка server/client
Docker PostgreSQL и MinIO для сервера; contract/component/integration/e2e-тесты
SQLite локальный кэш клиента

Приоритет настроек: флаги CLI → переменные окружения GOPHKEEPER_* → yaml → defaults. Примеры env — app/.env.example.


Быстрый старт (dev, HTTP)

Пошаговый сценарий: поднять инфраструктуру → миграции → сервер → клиент → пройти все команды.

Шаг 1. Сборка

Из корня репозитория:

make build
# бинарники: app/bin/server, app/bin/client
# version/date вшиваются через -ldflags (см. Makefile)

Без make:

go build -tags=go_json -o app/bin/server ./app/cmd/server
go build -tags=go_json -o app/bin/client ./app/cmd/client

Проверка:

./app/bin/client --version
./app/bin/server --version

Шаг 2. PostgreSQL (Docker)

docker run -d --name gophkeeper-postgres \
  -e POSTGRES_USER=gophkeeper \
  -e POSTGRES_PASSWORD=gophkeeper \
  -e POSTGRES_DB=gophkeeper \
  -p 5432:5432 \
  postgres:16-alpine

Дождаться готовности или:

docker logs -f gophkeeper-postgres
# database system is ready to accept connections

Остановка / удаление:

docker stop gophkeeper-postgres
docker rm gophkeeper-postgres

Шаг 3. MinIO (Docker)

MinIO хранит крупные binary-файлы vault. Бакет gophkeeper создаётся сервером при старте.

docker run -d --name gophkeeper-minio \
  -p 9000:9000 -p 9001:9001 \
  -e MINIO_ROOT_USER=minioadmin \
  -e MINIO_ROOT_PASSWORD=minioadmin \
  minio/minio server /data --console-address ":9001"

Веб-консоль MinIO: http://127.0.0.1:9001 (логин/пароль minioadmin).

Остановка / удаление:

docker stop gophkeeper-minio
docker rm gophkeeper-minio

Шаг 4. Миграции PostgreSQL

export DATABASE_DSN='postgres://gophkeeper:gophkeeper@localhost:5432/gophkeeper?sslmode=disable'
make migrate

или:

go run ./app/cmd/migrate -d "$DATABASE_DSN"

Миграции лежат в migrations/gophkeeper/. Клиентский SQLite мигрируется автоматически при первом запуске client (файл gophkeeper.db в cache dir).

Шаг 5. Переменные окружения сервера

Минимум для dev (можно положить в app/.env):

export GOPHKEEPER_AUTH_JWT_SECRET=change-me-in-dev
export GOPHKEEPER_DATABASE_URI='postgres://gophkeeper:gophkeeper@localhost:5432/gophkeeper?sslmode=disable'
export GOPHKEEPER_MINIO_ENDPOINT=localhost:9000
export GOPHKEEPER_MINIO_PUBLIC_ENDPOINT=localhost:9000
export GOPHKEEPER_MINIO_ACCESS_KEY=minioadmin
export GOPHKEEPER_MINIO_SECRET_KEY=minioadmin
export GOPHKEEPER_MINIO_USE_SSL=false
export GOPHKEEPER_MINIO_BUCKET=gophkeeper

Шаг 6. Запуск сервера

Терминал 1:

make run-server
# или: ./app/bin/server -c app/configs/server.yaml

Сервер слушает http://127.0.0.1:8080 (см. app/configs/server.yaml). gRPC по умолчанию выключен (grpc.address: "").

Шаг 7. Первый сеанс клиента

Терминал 2 (нужен интерактивный терминал — для master password и опционально login password):

make run-client
# эквивалент: ./app/bin/client -c app/configs/client.yaml

Регистрация и вход (пароль login — флагом; master password — отдельно, для vault):

./app/bin/client register --login alice --password 'login-secret'
./app/bin/client login --login alice --password 'login-secret'

Список items (без master password):

./app/bin/client items list
./app/bin/client sync pull

Создание text-item (запросит Master password в терминале):

./app/bin/client items create \
  --type text \
  --metadata '{"title":"note"}' \
  --data 'hello world'

Чтение (снова master password):

./app/bin/client items get --id 1

Выход из учётки:

./app/bin/client logout

Важно: команды vault (items create/get/update, rotate-dek, rewrap-master-password) запрашивают master password через скрытый ввод (term.ReadPassword). В pipe/скриптах без TTY на Windows будет ошибка The handle is invalid — выполняйте их в обычном терминале. Login password для register/login можно передать флагом --password.


Локальное хранилище клиента (SQLite)

Каталог кэша по умолчанию: ~/.cache/gophkeeper (переопределяется --cache-dir / GOPHKEEPER_CACHE_DIR).

Файл Содержимое
gophkeeper.db SQLite: снимок зашифрованных items, sync state, offline-очередь, сессии binary upload
~/.config/gophkeeper/session.json JWT access + refresh (файл сессии, флаг --session-file)

Отдельный контейнер SQLite не требуется — это встроенная БД в файле на диске клиента.

Очистка только локального кэша (сессия не трогается):

./app/bin/client cache clear

Глобальные флаги клиента

Указываются до подкоманды:

gophkeeper [глобальные флаги] <команда> [флаги команды]
Флаг Короткий Env Описание
--config -c CONFIG путь к yaml (app/configs/client.yaml)
--server -a GOPHKEEPER_IDENTITY_HTTP_SERVER_ADDRESS URL сервера при protocol=http (vault — тот же)
--protocol GOPHKEEPER_PROTOCOL http (default) или grpc
--grpc-address GOPHKEEPER_GRPC_ADDRESS адрес gRPC (host:port), обязателен при protocol=grpc
--log-level -l GOPHKEEPER_LOGGER_LEVEL уровень логов
--cache-dir GOPHKEEPER_CACHE_DIR каталог локального кэша / SQLite
--session-file GOPHKEEPER_IDENTITY_SESSION_PATH файл JWT-сессии
--tls-ca-file GOPHKEEPER_TLS_CA_FILE PEM CA для HTTPS (prod / self-signed)
--help -h дерево всех команд (cobra)

Примеры:

./app/bin/client -c app/configs/client.yaml --help
./app/bin/client -a http://127.0.0.1:8080 login --login alice --password secret
./app/bin/client --cache-dir /tmp/gk-test items list

Команды CLI

Identity

Команда Флаги Описание
register --login, --password регистрация + сохранение сессии
login --login, --password вход + сохранение сессии
logout — отзыв refresh на сервере + очистка session file
./app/bin/client register --login bob --password 'bob-pass'
./app/bin/client login --login bob --password 'bob-pass'
./app/bin/client logout

Если --password не указан, пароль запрашивается интерактивно.

Items (vault)

Типы: credential, card, text, binary.

Команда Флаги Master password Описание
items create --type, --metadata (JSON), --data (- = stdin) да создать item
items update --id, --type, --metadata, --data да обновить
items list — нет список (metadata, JSON)
items get --id, --save-to (только binary) да расшифровать и вывести / сохранить файл
items delete --id нет удалить
items rotate-dek --id да новый DEK, тот же MK
items rewrap-master-password — да (старый + новый ×2) пересохранить wrapped_dek для всех items
items resume-create — нет продолжить прерванный multipart create
items resume-update --id нет продолжить multipart update
items cancel-upload — нет отменить незавершённый multipart upload

Примеры:

# text
./app/bin/client items create --type text \
  --metadata '{"title":"pin"}' --data '1234'

# credential (данные — произвольный plaintext, формат на усмотрение клиента)
./app/bin/client items create --type credential \
  --metadata '{"site":"example.com"}' \
  --data '{"login":"user","password":"pass"}'

# card
./app/bin/client items create --type card \
  --metadata '{"label":"visa"}' \
  --data '{"number":"4111","exp":"12/30","cvv":"123","holder":"A B"}'

# binary (обязательно --save-to при get; --data - читает stdin)
./app/bin/client items create --type binary \
  --metadata '{"filename":"doc.pdf"}' \
  --data - < /path/to/file.pdf

./app/bin/client items get --id 4 --save-to /tmp/out.bin
./app/bin/client items delete --id 2
./app/bin/client items rotate-dek --id 1
./app/bin/client items rewrap-master-password   # старый MK → новый MK (дважды подтвердить)

При offline create/update/delete ставятся в очередь; после восстановления сети — sync push.

Binary upload: single vs multipart (чанки)

Порог задаётся в app/configs/server.yaml:

minio:
  multipart_part_size: 5242880   # 5 MiB
Размер ciphertext Режим Куда грузится
≤ 5 MiB single (presigned PUT) MinIO одним запросом
> 5 MiB multipart MinIO частями по multipart_part_size

Клиент шифрует файл целиком (envelope), затем режет ciphertext на части. Метаданные item остаются в Postgres; тело binary — в MinIO.

Пример 9 MiB (2 части: 5 + 4 MiB) — в интерактивном терминале:

dd if=/dev/urandom of=/tmp/big.bin bs=1M count=9

./app/bin/client items create --type binary \
  --metadata '{"filename":"big.bin","size":9437184}' \
  --data - < /tmp/big.bin
# Master password: ...

ID=<напечатанный id>
./app/bin/client items get --id "$ID" --save-to /tmp/out.bin
cmp /tmp/big.bin /tmp/out.bin

Обновление крупного binary (items update) использует тот же multipart-порог.

Команды resume — если загрузка оборвалась после InitBinaryUpload, но до Complete:

Команда Когда
items resume-create прерванный multipart create (сессия в SQLite клиента)
items resume-update --id N прерванный multipart update
items cancel-upload отменить незавершённую сессию

Обычный items create в одном запуске загружает все части подряд; resume нужен после сбоя сети/процесса между частями. Проверка: items resume-create без сессии → no persisted binary upload session.

Sync

Команда Флаги Описание
sync pull — скачать все items с сервера в локальный кэш
sync push — отправить offline-очередь на сервер
sync status — показать pending mutations
sync resolve --mutation-id, --strategy разрешить конфликт (local, server, lww)
./app/bin/client sync pull
./app/bin/client sync status
./app/bin/client sync push
./app/bin/client sync resolve --mutation-id <uuid> --strategy server

При недоступном сервере — offline read-write: items list / items get из кэша; create/update/delete — в очередь, затем sync push.

Типичный offline-поток:

# сервер недоступен — create/delete попадают в очередь
./app/bin/client items create --type text --metadata '{}' --data 'offline'
./app/bin/client sync status          # видны pending mutations
# сервер снова доступен:
./app/bin/client sync push
# при конфликте:
./app/bin/client sync resolve --mutation-id <uuid> --strategy local|server|lww
./app/bin/client sync push

Cache

Команда Описание
cache clear удалить локальный снимок items (SQLite cache)

Интерактивный режим (REPL)

./app/bin/client -c app/configs/client.yaml session
# gophkeeper> login --login alice --password secret
# gophkeeper> items list
# gophkeeper> exit

Глобальные флаги (-c, -a, --protocol, …) задаются при запуске session. Строки в REPL — только команды cobra без повторного partitionArgs. Конфиг и remote gateways (HTTP или gRPC) фиксируются на старте процесса.


gRPC (опционально)

По умолчанию везде HTTP. gRPC включается явно на сервере и клиенте.

Сервер — непустой grpc.address в yaml или флаг --grpc-address:

# app/configs/server.yaml
grpc:
  address: "127.0.0.1:9090"

HTTP REST при этом остаётся на server.address (8080). В prod те же cert_file / key_file, что и для HTTPS, применяются и к gRPC.

Клиент — protocol: grpc и адрес:

# app/configs/client.yaml
protocol: grpc
grpc:
  address: "127.0.0.1:9090"

или флаги:

./app/bin/client --protocol grpc --grpc-address 127.0.0.1:9090 login --login alice --password secret

Production (HTTPS + TLS)

1. Сертификаты (dev CA)

make certs
# certs/ca.pem, certs/server.crt, certs/server.key

2. Env для prod

export GOPHKEEPER_AUTH_JWT_SECRET=change-me-in-production
export GOPHKEEPER_DATABASE_URI='postgres://gophkeeper:gophkeeper@localhost:5432/gophkeeper?sslmode=disable'
export GOPHKEEPER_MINIO_ENDPOINT=localhost:9000
export GOPHKEEPER_MINIO_PUBLIC_ENDPOINT=localhost:9000
export GOPHKEEPER_MINIO_ACCESS_KEY=minioadmin
export GOPHKEEPER_MINIO_SECRET_KEY=minioadmin
export GOPHKEEPER_MINIO_USE_SSL=false   # true, если MinIO тоже на HTTPS
export GOPHKEEPER_MINIO_BUCKET=gophkeeper

Если MinIO в Docker на HTTP, а в server.prod.yaml стоит minio.use_ssl: true, переопределите GOPHKEEPER_MINIO_USE_SSL=false.

3. Запуск

make run-server-prod   # https://0.0.0.0:8443
make run-client-prod   # client.prod.yaml + tls.ca_file: certs/ca.pem

Клиент: https://127.0.0.1:8443. Для публичного CA (Let's Encrypt) tls.ca_file можно не задавать.

Dev Prod
Конфиги server.yaml / client.yaml server.prod.yaml / client.prod.yaml
URL http://127.0.0.1:8080 https://127.0.0.1:8443
TLS на сервере нет server.crt + server.key
CA на клиенте не нужен certs/ca.pem для self-signed
gRPC выключен (grpc.address: "") опционально; при protocol: grpc на клиенте — tls.ca_file обязателен в client.prod.yaml

TLS защищает транспорт; шифрование vault (MK/DEK) — отдельный слой поверх HTTPS/gRPC.


Глобальные флаги сервера

Флаг Env Описание
-c / --config CONFIG yaml конфиг
--address GOPHKEEPER_SERVER_ADDRESS listen address
--database-uri GOPHKEEPER_DATABASE_URI Postgres DSN (обязателен)
--jwt-secret GOPHKEEPER_AUTH_JWT_SECRET секрет JWT (обязателен)
--log-level GOPHKEEPER_LOGGER_LEVEL уровень логов
--tls-cert GOPHKEEPER_SERVER_CERT_FILE TLS cert (prod)
--tls-key GOPHKEEPER_SERVER_KEY_FILE TLS key (prod)
--grpc-address GOPHKEEPER_GRPC_ADDRESS gRPC listen (host:port); пусто — gRPC выключен

MinIO — через env / yaml (GOPHKEEPER_MINIO_*), см. app/configs/server.yaml:

Поле yaml Env Описание
minio.endpoint GOPHKEEPER_MINIO_ENDPOINT адрес MinIO для сервера
minio.public_endpoint GOPHKEEPER_MINIO_PUBLIC_ENDPOINT адрес в presigned URL для клиента
minio.access_key GOPHKEEPER_MINIO_ACCESS_KEY ключ
minio.secret_key GOPHKEEPER_MINIO_SECRET_KEY секрет
minio.bucket GOPHKEEPER_MINIO_BUCKET имя бакета (по умолчанию gophkeeper)
minio.use_ssl GOPHKEEPER_MINIO_USE_SSL HTTPS к MinIO
minio.multipart_part_size GOPHKEEPER_MINIO_MULTIPART_PART_SIZE размер части multipart (байты)

Postgres и MinIO обязательны для старта сервера.


Тесты

Пирамида: unit → contract → integration → component → e2e. Для contract, component, integration и e2e нужен Docker (testcontainers: Postgres + MinIO).

make test              # unit
make test-contract     # контракт client - server
make test-integration  # репозитории Postgres / MinIO / SQLite
make test-component    # HTTP-сценарии сервера: identity, vault
make test-e2e          # полные сценарии: auth, items, sync, offline
make test-all          # все цели выше

Генерация кода

После изменения HTTP dto (struct, json-теги):

make generate-easyjson

После изменения converter-интерфейсов в adapters/.../converter/ (entity - model, intermodule):

make generate-goverter

После изменения port-интерфейсов:

make generate-mocks

После изменения .proto (identity, vault):

make generate-proto

Make-цели

make build              # app/bin/server + client
make build-all          # кросс-сборка linux/windows/darwin → app/bin/dist/
make certs              # dev TLS
make run-server         # HTTP dev
make run-client         # HTTP dev client
make run-server-prod    # HTTPS prod
make run-client-prod    # HTTPS prod client
make migrate            # Postgres миграции (нужен DATABASE_DSN)
make test               # unit-тесты (см. раздел «Тесты»)
make test-contract      # контракт client ↔ server
make test-component     # component-тесты сервера
make test-integration   # postgres/minio/sqlite integration
make test-e2e           # полные сценарии (testcontainers)
make test-all           # всё вместе
make cover-unit         # покрытие unit
make cover              # покрытие unit + integration
make generate-easyjson  # easyjson для HTTP dto
make generate-goverter  # goverter для adapters/.../converter
make generate-mocks     # gomock для application/port
make generate-proto     # protobuf / gRPC stubs

About

Client-server password manager in Go: secure storage and sync of logins, cards, text and binary data with a cross-platform CLI.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages