# Synapse Backupper

Инструмент для резервного копирования PostgreSQL [Synapse](https://github.com/element-hq/synapse) с композитным шифрованием на основе двух механизмов обмена ключами (dual-KEM).

## Обзор

`synapse-backupper` создает зашифрованные дампы PostgreSQL с использованием **композитной** схемы шифрования, которая объединяет постквантовый KEM (ML-KEM-768) и классический KEM (X25519). Для расшифровки резервной копии требуются оба ключа (модель AND). Инструмент поддерживает два режима работы: разовое резервное копирование (`backup`) и планировщик (`run`), который выполняет копирование по расписанию cron.

Копии хранятся локально на диске; устаревшие копии автоматически удаляются по возрасту.

## Установка

Сборка Docker-образа:

```bash
make docker-build
```

В результате собирается образ `synapse-backupper:latest`. Внутри образа находится скомпилированный бинарник, клиентские утилиты PostgreSQL, а сам контейнер запускается от непривилегированного пользователя.

## Быстрый старт

### 1. Генерация ключей шифрования

Сгенерируйте обе пары ключей (постквантовую и классическую) с помощью подкоманды `keygen`:

```bash
mkdir -p ./keys
docker run --rm -u root -v "$(pwd)/keys:/keys" synapse-backupper:latest \
    keygen --type both --out-prefix /keys/synapse
chown -R "$(id -u):$(id -g)" ./keys
```

> **Примечание:** Образ контейнера по умолчанию запускается от непривилегированного пользователя `app`. Подкоманда `keygen` должна писать в смонтированный с хоста каталог, поэтому здесь она запускается от `root`, а затем владение файлами возвращается текущему пользователю. Если вы не работаете от `root`, скорректируйте команду `chown` (например, через `sudo`).

В каталоге `./keys/` появятся четыре файла:

| Файл | Назначение |
|------|------------|
| `synapse.pq.pub.pem` | Постквантовый публичный ключ (для резервного копирования) |
| `synapse.pq.priv.pem` | Постквантовый приватный ключ (для восстановления, хранить офлайн) |
| `synapse.classical.pub.pem` | Классический публичный ключ (для резервного копирования) |
| `synapse.classical.priv.pem` | Классический приватный ключ (для восстановления, хранить офлайн) |

**Важно:** приватные ключи никогда не следует монтировать в контейнер, который делает резервные копии. Храните их офлайн или на отдельном хосте, предназначенном только для восстановления.

### 2. Режимы работы

#### Режим планировщика (`run`)

Команда по умолчанию запускает планировщик по расписанию cron и HTTP-эндпоинт для проверки здоровья:

```bash
docker run -d -u root \
    --name synapse-backupper \
    -v "$(pwd)/keys:/keys:ro" \
    -v "$(pwd)/backups:/backups" \
    -e APP_PG_HOST=db \
    -e APP_PG_DATABASE=postgres \
    -e APP_PG_USER=synapse \
    -e APP_PG_PASSWORD=changeme \
    -e APP_PQ_PUBLIC_KEY_PATH=/keys/synapse.pq.pub.pem \
    -e APP_CLASSICAL_PUBLIC_KEY_PATH=/keys/synapse.classical.pub.pem \
    -e APP_BACKUP_DIR=/backups \
    synapse-backupper:latest
```

По умолчанию резервное копирование выполняется ежедневно в `03:00`. Расписание задается параметром `backup.cron` в конфигурационном файле или переменной окружения `APP_BACKUP_CRON`.

#### Разовое резервное копирование (`backup`)

Выполнить одну резервную копию немедленно:

```bash
docker run --rm -u root \
    -v "$(pwd)/keys:/keys:ro" \
    -v "$(pwd)/backups:/backups" \
    -e APP_PG_HOST=5432 \
    -e APP_PG_DATABASE=postgres \
    -e APP_PG_USER=synapse \
    -e APP_PG_PASSWORD=changeme \
    -e APP_PQ_PUBLIC_KEY_PATH=/keys/synapse.pq.pub.pem \
    -e APP_CLASSICAL_PUBLIC_KEY_PATH=/keys/synapse.classical.pub.pem \
    -e APP_BACKUP_DIR=/backups \
    synapse-backupper:latest backup
```

Результирующий файл получает имя `synapse-<timestamp>.dump.pqenc` и сохраняется в каталог резервных копий. Дамп создается в формате PostgreSQL custom (`--format=custom`), который ожидает `pg_restore`.

### 3. Восстановление на отдельном хосте

Процедура восстановления специально выполняется на хосте, который **не** имеет доступа к базе данных. Достаточно только приватных ключей.

```bash
docker run --rm \
    -v "$(pwd)/keys:/keys:ro" \
    -v "$(pwd)/backups:/backups:ro" \
    -v "$(pwd)/restored:/out" \
    synapse-backupper:latest restore \
    --in /backups/synapse-20260102-150405.dump.pqenc \
    --privkey-pq /keys/synapse.pq.priv.pem \
    --privkey-classical /keys/synapse.classical.priv.pem \
    --out /out/restored.dump
```

После этого восстановите расшифрованный дамп стандартными средствами PostgreSQL:

```bash
pg_restore --clean --if-exists --dbname=synapse restored.dump
```

## Конфигурация

Приоритет источников настроек: флаги командной строки → переменные окружения `APP_*` → файл конфигурации → значения по умолчанию.

Примеры в разделе «Быстрый старт» используют переменные окружения. Префикс — `APP_`, вложенные ключи разделяются подчеркиванием, например:

- `APP_PG_HOST`
- `APP_PG_DATABASE`
- `APP_BACKUP_DIR`
- `APP_PQ_PUBLIC_KEY_PATH`
- `APP_CLASSICAL_PUBLIC_KEY_PATH`

Чтобы использовать файл конфигурации, сгенерируйте пример и смонтируйте его в контейнер:

```bash
docker run --rm synapse-backupper:latest generate-config --lang ru > config.yaml
```

Отредактируйте `config.yaml`, затем смонтируйте его в `/config.yaml` при запуске контейнера:

```bash
docker run -d \
    --name synapse-backupper \
    -v "$(pwd)/config.yaml:/config.yaml" \
    -v "$(pwd)/keys:/keys:ro" \
    -v "$(pwd)/backups:/backups" \
    synapse-backupper:latest
```

Переменные окружения переопределяют значения из файла конфигурации.

## Docker Compose

Планировщик можно запустить через Docker Compose. Пример `docker-compose.yml`:

```yaml
version: '3.8'
services:
  synapse-backupper:
    image: synapse-backupper:latest
    volumes:
      - ./backups:/backups
      - ./keys:/keys:ro
    environment:
      - APP_PG_HOST=synapse-pg
      - APP_PG_DATABASE=synapse
      - APP_PG_USER=synapse
      - APP_PG_PASSWORD=secret
      - APP_PQ_PUBLIC_KEY_PATH=/keys/synapse.pq.pub.pem
      - APP_CLASSICAL_PUBLIC_KEY_PATH=/keys/synapse.classical.pub.pem
      - APP_BACKUP_DIR=/backups
      - APP_BACKUP_CRON=0 0 3 * * *
    healthcheck:
      test: ["CMD", "synapse-backupper", "healthcheck"]
      interval: 30s
      timeout: 3s
      start_period: 10s
    restart: unless-stopped
```

> **Важно:** В каталоге `./keys` должны находиться только публичные ключи (`*.pub.pem`). Перед запуском контейнера переместите приватные ключи (`*.priv.pem`) на офлайн-хост или хост, предназначенный только для восстановления.
>
> Перед промышленным развёртыванием замените `APP_PG_PASSWORD=secret` на надёжный пароль; указанное значение только пример.

Запуск планировщика:

```bash
docker compose up -d
```

## Модель безопасности

### Многоуровневая защита

Резервные копии шифруются по **композитной** схеме: общий секрет вычисляется независимо от постквантового и классического KEM, а полезная нагрузка шифруется симметричным ключом, полученным из обоих секретов. Злоумышленник должен взломать **оба** механизма, чтобы получить данные.

### Модель AND

Для расшифровки требуются **оба** приватных ключа. Команда `restore` завершится с ошибкой, если хотя бы один ключ отсутствует или неверен. Это исключает компрометацию копий при компрометации одного ключа.

### Plug-and-play KEM

Реализации KEM регистрируются в runtime-реестре по идентификатору схемы. Новые постквантовые или классические алгоритмы можно добавлять без изменения логики резервного копирования и восстановления.

### Изоляция приватных ключей

Приватные ключи **не нужны** при создании резервной копии. Контейнер, выполняющий бэкап, монтирует только публичные ключи (`:ro`). Приватные ключи должны находиться на отдельном хосте восстановления или в офлайн-хранилище, что сужает зону поражения при компрометации сервера резервного копирования.

## Ограничения

Это MVP-релиз. Следующие возможности **не реализованы**:

- **S3 / объектное хранилище** — копии пишутся только в локальный каталог.
- **Развертывание в Kubernetes** — Helm-чарт и оператор не предоставляются.
- **Потоковое / инкрементальное резервное копирование** — каждый запуск создает полный `pg_dump`.
- **Поддержка нескольких баз данных** — предполагается одна база данных на инстанс.

## Разработка

Запуск unit-тестов:

```bash
make test
```

Запуск сквозного интеграционного теста (требуется Docker):

```bash
make integration-test
```

Кросс-компиляция:

```bash
make cross-build
```

## Лицензия

MIT
