Tropic Host

Установка и настройка Vaultwarden на VPS: собственный защищенный менеджер паролей в Docker

36 мин чтения
Tropic

Краткий вывод: Для надежного и безопасного развертывания менеджера паролей Vaultwarden (Bitwarden) достаточно минимального KVM VPS с 1 vCPU, 1 ГБ RAM и 10–20 ГБ NVMe накопителя, так как оптимизированный бэкенд на Rust потребляет всего 30–60 МБ оперативной памяти. Критическими условиями для работы клиентских приложений являются обязательное наличие SSL-сертификата (Web Crypto API в браузерах блокирует доступ без HTTPS), настройка SMTP для 2FA-верификации и организация ежедневных бэкапов базы данных в удаленное S3-хранилище.


Содержание

  1. Аппаратные требования и сайзинг KVM VPS под Vaultwarden
  2. Сравнительная матрица: Vaultwarden vs Официальный Bitwarden vs 1Password vs KeePassXC
  3. Подготовка сервера Ubuntu 24.04: безопасность хоста, UFW файрвол и Docker Engine
  4. Развертывание Vaultwarden в Docker Compose: переменные окружения и токены
  5. Настройка Nginx Reverse Proxy и автоматического SSL Let's Encrypt
  6. Интеграция SMTP для отправки почты и двухфакторная аутентификация (2FA)
  7. Резервное копирование базы данных: режим SQLite WAL и безопасная выгрузка в S3
  8. Чек-лист hardening-безопасности: защита панели администратора и Fail2ban
  9. Часто задаваемые вопросы (FAQ)

Аппаратные требования и сайзинг KVM VPS под Vaultwarden

Планирование вычислительных мощностей под менеджер паролей кардинально зависит от выбранной реализации серверного стека. Официальный сервер Bitwarden спроектирован для enterprise-инфраструктур: он представляет собой микросервисный комплекс из более чем 10 изолированных контейнеров (api, identity, sso, admin, icons, notifications, attachments, nginx, events и базы данных Microsoft SQL Server / MariaDB). Этот стек на базе ASP.NET Core и MSSQL требует как минимум 2.5–3.5 GB оперативной памяти только для успешного старта всех подов и прохождения healthcheck-проверок. Попытка запустить оригинальный Bitwarden на виртуальном сервере с 1–2 GB RAM гарантированно приводит к срабатыванию механизма Linux OOM Killer (Out Of Memory) и аварийному завершению процессов СУБД.

Vaultwarden решает эту архитектурную проблему за счет полной переработки бэкенда на языке Rust. Сервис компилируется в единый статически слинкованный бинарный файл, использующий асинхронный фреймворк Rocket/Actix и встроенный легковесный веб-интерфейс. Потребление оперативной памяти (Resident Set Size, RSS) у запущенного контейнера Vaultwarden в состоянии покоя составляет всего 30–45 MB. Под умеренной нагрузкой при обработке пакетной синхронизации хранилищ и валидации сессий пиковое потребление RAM редко превышает 60–80 MB.

Для стабильной работы стека требуется аппаратная виртуализация KVM, так как в контейнерных средах OpenVZ/LXC разделяемое ядро хоста ограничивает изоляцию подсистем cgroups v2, блокирует кастомную настройку планировщиков дискового ввода-вывода и искажает учет процессорного времени (CPU Steal Time, %st).

Когда планируется развертывание менеджера паролей Vaultwarden на сервере, архитектура и сайзинг требуют точного разделения целевых профилей нагрузки: персонально-семейный контур и корпоративная среда.

Сравнительная матрица сайзинга KVM VPS

В таблице ниже сопоставлены аппаратные профили для различных сценариев эксплуатации Vaultwarden в сравнении с базовыми требованиями официального дистрибутива Bitwarden.

Параметр спецификации Семейный профиль (1–5 пользователей) Малая команда / Стартап (5–20 пользователей) Корпоративный контур (20–100 сотрудников) Официальный Bitwarden (Базовый минимум)
Выделенные vCPU 1 vCPU (допустим Shared, %st < 5%) 1–2 vCPU (Shared / Balanced) 2–4 vCPU (Dedicated Core, 0% %st) Минимум 2–4 vCPU
Оперативная память (RAM) 512 MB – 1 GB 1 GB – 2 GB 2 GB – 4 GB 4 GB (рекомендовано 8 GB)
Фактическое потребление RAM стеком 35–60 MB (Vaultwarden) + 80 MB (Caddy) 60–120 MB (Vaultwarden) + Nginx/Caddy 150–300 MB + 300–500 MB (PostgreSQL) 2500–3500 MB (NET Core + MSSQL)
Тип хранилища и объем 10–15 GB NVMe SSD 20–30 GB NVMe SSD 40–80 GB NVMe (RAID1 / CEPH) 40+ GB SSD/NVMe
Дисковый ввод-вывод (IOPS) 500–1 000 IOPS, p99 < 5ms 1 500–2 500 IOPS, p99 < 2ms 3 000–5 000+ IOPS, p99 < 1ms 2 000+ IOPS (MSSQL logs write bottleneck)
Архитектура базы данных Встроенная SQLite (режим WAL) SQLite (WAL) либо PostgreSQL Выделенный контейнер PostgreSQL 15/16 Microsoft SQL Server / MariaDB
Сетевой канал (Bandwidth) 100 Mbps (лимит 500 GB/мес) 100–300 Mbps 1 Gbps (симметричный гарантированный) 100–500 Mbps

Детальный сайзинг под сценарии нагрузки

1. Персональное и семейное использование (1–5 пользователей)

В данном сценарии суммарный размер базы данных учетных записей (ciphers), включая папки и защищенные заметки, редко превышает 15–30 MB. * Процессор и память: Достаточно минимального инстанса с 1 vCPU и 1 GB RAM (на дистрибутивах Debian 12 / Ubuntu 24.04 LTS без графического окружения базовая ОС потребляет 120–160 MB RAM, Docker Engine — около 80 MB, контейнер Caddy или Nginx — 30–50 MB, сам Vaultwarden — до 60 MB). Запас оперативной памяти защищает от пиков при ночном создании дампов. * Дисковая подсистема: Встроенный движок SQLite полностью закрывает потребности, но требует активации режима опережающей записи (Write-Ahead Logging). По умолчанию SQLite использует откат журнала (rollback journal), что вызывает синхронные блокировки файлов. Активация WAL снижает латентность записи при конкурентных обращениях мобильных клиентов:

# Проверка и принудительное включение WAL-режима для рабочей базы
sqlite3 /vw-data/db.sqlite3 "PRAGMA journal_mode=WAL;"
sqlite3 /vw-data/db.sqlite3 "PRAGMA synchronous=NORMAL;"

2. Корпоративный контур (20–100 активных сотрудников)

При масштабировании до десятков пользователей узким местом становятся не криптографические операции (шифрование и расшифровка vault data осуществляются на стороне клиента в браузере или приложении по схеме Zero-Knowledge), а параллельные транзакции и постоянные WebSocket-соединения. * Процессор (vCPU): Рекомендуется минимум 2 выделенных ядра с базовой частотой от 3.0 GHz. Важно контролировать метрику CPU Steal Time (%st в выводе утилиты top или mpstat): если соседние виртуальные машины на гипервизоре хостера утилизируют пул vCPU и показатель %st превышает 3–5%, синхронизация клиентских приложений будет зависать по таймауту. * Оперативная память и СУБД: Встроенная SQLite категорически не подходит для одновременной записи 50+ клиентами из-за блокировки уровня всей базы (database is locked). Для корпоративного сектора используется внешний контейнер PostgreSQL. Серверу требуется от 2 до 4 GB RAM: PostgreSQL выделяет shared_buffers (рекомендуется выставить 25% от доступного объема ОЗУ — 512 MB–1 GB) для кэширования индексов страниц памяти. * Накопители (NVMe): Критически важен параметр задержки случайной записи (Random Write Latency p99). PostgreSQL активно пишет WAL-журналы на диск при каждой модификации пароля, генерации аварийных кодов или аудите событий (events). Требуются накопители с показателем IOPS не менее 3000 и гарантированной задержкой fsync менее 1 миллисекунды.


Аппаратная изоляция и лимиты cgroups v2

Для предотвращения деградации операционной системы при непредвиденных утечках памяти или атаках типа «отказ в обслуживании» (DoS) на API эндпоинты авторизации, конфигурация развертывания обязана содержать жесткие аппаратные лимиты ресурсов ядра Linux.

Ниже приведена боевая спецификация docker-compose.yml с жестким ограничением квот подсистемы cgroups v2 для стека Vaultwarden + PostgreSQL:

version: '3.8'

services:
  vaultwarden:
    image: vaultwarden/server:latest
    container_name: vaultwarden_app
    restart: unless-stopped
    environment:
      - WEBSOCKET_ENABLED=true
      - DATABASE_URL=postgresql://vw_user:StrongSecretPass@postgres:5432/vaultwarden
      - SENDS_ALLOWED=true
      - EMERGENCY_ACCESS_ALLOWED=true
    volumes:
      - /opt/vaultwarden/data:/data
    deploy:
      resources:
        limits:
          cpus: '1.50'
          memory: 512M
        reservations:
          cpus: '0.25'
          memory: 64M
    depends_on:
      postgres:
        condition: service_healthy

  postgres:
    image: postgres:16-alpine
    container_name: vaultwarden_db
    restart: unless-stopped
    environment:
      - POSTGRES_DB=vaultwarden
      - POSTGRES_USER=vw_user
      - POSTGRES_PASSWORD=StrongSecretPass
    volumes:
      - /opt/vaultwarden/pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U vw_user -d vaultwarden"]
      interval: 5s
      timeout: 5s
      retries: 5
    deploy:
      resources:
        limits:
          cpus: '2.00'
          memory: 1024M
        reservations:
          cpus: '0.50'
          memory: 256M

Тюнинг системных параметров ядра Linux (sysctl)

При обслуживании пула из сотен десктопных и мобильных клиентов, удерживающих постоянные WebSocket-каналы для мгновенного обновления хранилища (/notifications/hub), сервер исчерпывает стандартные лимиты сокетов и дескрипторов файлов.

Для оптимизации сетевого стека ядра Linux в файл /etc/sysctl.d/99-vaultwarden.conf вносятся следующие директивы:

# Увеличение очереди соединений для защиты от дропов при массовом синке
net.core.somaxconn = 4096
net.ipv4.tcp_max_syn_backlog = 4096

# Расширение диапазона эфемерных портов
net.ipv4.ip_local_port_range = 10240 65535

# Быстрое переиспользование TIME_WAIT сокетов для keepalive соединений
net.ipv4.tcp_tw_reuse = 1

# Защита от OOM при агрессивном выделении виртуальной памяти
vm.overcommit_memory = 1

# Максимальный лимит открытых файловых дескрипторов на систему
fs.file-max = 2097152

Применение параметров выполняется без перезагрузки ноды:

sysctl --system

Диагностика реальной утилизации мощностей

После развертывания сервиса необходимо провести инструментальный замер потребления ресурсов под рабочей нагрузкой. Анализ распределения памяти и нагрузки на ядро процессора выполняется связкой стандартных утилит:

# Мониторинг потребления CPU, памяти и сетевого I/O контейнерами в реальном времени
docker stats --no-stream --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.MemPerc}}\t{{.NetIO}}\t{{.BlockIO}}"

# Проверка распределения процессорного времени и отсутствия steal time хоста (%st)
mpstat -P ALL 1 5

Если по метрике BlockIO наблюдается постоянное накопление задержек (I/O Wait, %iowait > 10% в iostat -xz 1), это свидетельствует о медленном виртуальном диске провайдера, что требует немедленного переноса каталога /opt/vaultwarden на изолированный NVMe-пул с прямой поддержкой команд fstrim.

Сравнительная матрица: Vaultwarden vs Официальный Bitwarden vs 1Password vs KeePassXC

Выбор архитектуры централизованного хранения секретов в инфраструктуре определяется компромиссом между аппаратными затратами, криптографической моделью, сетевой топологией синхронизации и стоимостью лицензирования. На практике для хостинга серверных компонентов критична аппаратная виртуализация KVM, исключающая лимиты ядра на системные вызовы и монтирование блочных устройств, характерные для устаревших контейнеров OpenVZ/LXC.

Разница между стеками становится очевидной при анализе внутреннего устройства демонов: официальный стек Bitwarden разворачивается в виде 10–12 связанных микросервисных контейнеров на платформе ASP.NET Core с СУБД Microsoft SQL Server (либо MariaDB), в то время как Vaultwarden представляет собой единый монолитный бинарный файл, скомпилированный на Rust поверх асинхронного рантайма Tokio и HTTP-фреймворка Rocket.

Критерий Vaultwarden (ex-Bitwarden_RS) Официальный Bitwarden (Self-Hosted) 1Password (Business/Teams) KeePassXC
Архитектура и стек Монолитный бинарник на Rust (Tokio/Rocket), SQLite/PostgreSQL/MySQL 10+ Docker-контейнеров (.NET Core, Node.js, MSSQL/PostgreSQL, Nginx) Проприетарный SaaS-бэкенд (AWS) + SCIM/Connect bridge Локальное приложение (C++/Qt), без серверного компонента
Модель размещения Self-hosted (полный суверенитет данных) Self-hosted (полный суверенитет данных) Публичное облако (SaaS, юрисдикции США/ЕС) Локальный оффлайн-файл базы (.kdbx)
Потребление RAM (Idle / 50 активных сессий) ~28 МБ / ~65–85 МБ RSS 2.4 ГБ / 3.8–4.5 ГБ RSS 0 МБ (на локальном сервере клиента) 0 МБ сервер / ~45 МБ локальный клиент
Минимальные требования к ноде 1 vCPU, 512 МБ RAM, 5 ГБ NVMe 2–4 vCPU, 4 ГБ RAM, 30 ГБ NVMe Не применимо (SaaS) Не применимо (локальная рабочая станция)
Синхронизация и Push-события WebSocket (/notifications/hub) в едином процессе Отдельный сервис WebSocket push-уведомлений Проприетарный push-протокол (APNs/FCM + WebSockets) Отсутствует (требует сторонний transport: WebDAV, Syncthing)
Криптографические примитивы KDF PBKDF2 (SHA-256) / Argon2id (m=64MB, t=3, p=4) PBKDF2 (SHA-256) / Argon2id (m=64MB, t=3, p=4) PBKDF2 + SRP (Secure Remote Password) + 128-bit Secret Key Argon2id / Argon2d / AES-KDF
Шифрование payload AES-CBC-256 с HMAC-SHA-256 / AES-GCM AES-CBC-256 с HMAC-SHA-256 / AES-GCM AES-GCM-256 (256-bit Master Key + Secret Key) ChaCha20-Poly1305 / AES-KDBX4 (256-bit)
Аудит исходного кода Неофициальный форк, аудит силами комьюнити Регулярный сторонний аудит (Cure53, Insight Risk) Регулярный сторонний аудит (Cure53, ISE) Регулярный аудит десктопного кода (Cure53, SEC Consult)
Клиентская экосистема Нативные клиенты Bitwarden (Desktop, Browser, Mobile, CLI) Нативные клиенты Bitwarden (Desktop, Browser, Mobile, CLI) Собственные нативные клиенты + CLI-утилита op Локальный клиент KeePassXC, форки под Android/iOS
Разрешение конфликтов (Concurrency) Last-Write-Wins с серверной транзакцией БД Блокировки на уровне MSSQL/PostgreSQL Серверный three-way merge графа версий Блокировка файла; высокий риск race condition при sync
TCO (25 пользователей, 1 год) Стоимость VPS (~$40–$60/год) Стоимость VPS ($240–$480) + лицензия ($1 200/год) $2 397/год ($7.99/пользователь/месяц) $0 лицензии, расходы на настройку хранилища

Профилирование потребления ресурсов и поведение cgroups

Разница в потреблении системных ресурсов между легковесным форком и официальным стеком принципиальна при сайзинге виртуальных машин. Официальный Bitwarden требует развертывания тяжеловесного СУБД-движка MSSQL или корпоративного инстанса PostgreSQL, набора фоновых служб (Admin, API, Identity, Notifications, Sso, Events) и проксирующего Nginx. В условиях жестких лимитов cgroups v2 такой стек в момент холодного старта генерирует резкий всплеск ввода-вывода (IOPS) и утилизирует до 2 ГБ виртуальной памяти (VIRT) только на прогрев сред выполнения .NET CLR.

В свою очередь, при корректной установке и настройке Vaultwarden на виртуальном сервере контейнер демонстрирует минимальный resident set size (RSS). За счет сборки бинарника с использованием аллокатора памяти jemalloc или стандартного musl/glibc аллокатора с отключенной избыточной фрагментацией кучи, демон стабильно удерживает потребление в пределах 30–40 МБ оперативной памяти даже при подключении десятков веб-сокетов.

Инструментальная проверка лимитов через cgroups на хосте подтверждает линейную стабильность Vaultwarden:

# Чтение текущего потребления памяти контейнером напрямую из псевдофайловой системы cgroups v2
cat /sys/fs/cgroup/system.slice/docker-$(docker inspect --format '{{.Id}}' vaultwarden).scope/memory.current | awk '{print $1/1024/1024 " MB"}'

# Ограничение ресурсов в runtime через drop-in обновление параметров контейнера
docker update --memory 256M --memory-swap 512M --cpus 1.0 vaultwarden

При 100 активных клиентах, непрерывно отправляющих запросы на синхронизацию шифротекста, задержка обработки системного вызова epoll_wait в связке с Rust Tokio не превышает p99 < 8ms, тогда как стек официального Bitwarden под аналогичной нагрузкой требует запаса минимум в 3–4 ГБ RAM для предотвращения триггера oom-killer в моменты выполнения процедур очистки мусора (Garbage Collection).


Криптографический анализ: KDF, устойчивость к ASIC и Zero-Knowledge

Все четыре решения реализуют модель сквозного шифрования (Zero-Knowledge Encryption), при которой расшифровка полезной нагрузки (паролей, заметок, SSH-ключей) происходит исключительно на стороне клиента в непривилегированном пользовательском пространстве. Сервер оперирует исключительно нечитаемыми блоками шифротекста и солеными хэшами аутентификации.

Однако алгоритмы формирования мастер-ключа (Key Derivation Function, KDF) и векторы защиты от перебора по словарю на GPU/ASIC-фермах существенно различаются:

  1. PBKDF2-HMAC-SHA256: Долгое время являлся стандартом де-факто для Bitwarden и Vaultwarden. Его критическая уязвимость перед параллельными аппаратными атаками заключается в низком потреблении памяти: вычисление SHA-256 тривиально распараллеливается на тысячах ядер видеокарт и специализированных чипах. Увеличение числа итераций до 600 000 лишь частично компенсирует вычислительную асимметрию, вызывая заметные фризы интерфейса мобильных клиентов при разблокировке.
  2. Argon2id (Рекомендуемый стандарт Vaultwarden / Bitwarden / KeePassXC): Гибридный алгоритм, сочетающий устойчивость к атакам по сторонним каналам (timing attacks, взято из Argon2i) и защиту от перебора на GPU/ASIC с оптимизацией заполнения памяти (memory-hard, взято из Argon2d). При конфигурации параметров:
  3. Memory Cost ($m$): $64\text{ МБ}$ ($65536\text{ КБ}$)
  4. Time Cost ($t$): $3\text{ итерации}$
  5. Parallelism ($p$): $4\text{ потока}$

Атакующий вынужден выделять по 64 МБ быстрой памяти на каждый параллельный поток перебора, что экономически нивелирует преимущество видеокарт. 3. Двухключевая схема 1Password (Secret Key + Master Password): 1Password использует уникальную надстройку. Помимо мастер-пароля, на клиенте генерируется локальный 128-битный криптографически стойкий ключ (Secret Key). Мастер-ключ формируется объединением обоих секретов через алгоритм PBKDF2-HMAC-SHA256 (или Argon2id в актуальных версиях) с последующей аутентификацией по протоколу SRP (Secure Remote Password, RFC 5054). Это исключает возможность offline-брутфорса базы даже в случае полной компрометации облачной инфраструктуры 1Password, так как у атакующего отсутствует энтропия Secret Key (128 бит истинной случайности).


Синхронизация, Push-события и обработка состояний гонки (Race Conditions)

Топология доставки изменений на клиенты определяет надежность работы команды:

  • Vaultwarden и Bitwarden: Используют двунаправленный протокол WebSockets. Клиенты удерживают постоянное TCP-соединение с эндпоинтом /notifications/hub. При коммите изменений любым членом организации сервер пушит минимальный JSON-пейлорд с метаданными синхронизации, инициируя фоновый GET /api/sync. Обработка одновременной модификации одной и той же записи реализована по принципу Last-Write-Wins (LWW) на основе временных меток ревизий базы данных.
  • 1Password: Реализует проприетарную систему распределенного версионирования объектов с графовой структурой связей. Конфликты изменений разрешаются трехсторонним слиянием (three-way merge) на сервере без перезаписи полей, если редактировались непересекающиеся атрибуты элемента.
  • KeePassXC: Принципиально лишен собственного транспортного уровня. Синхронизация между устройствами возлагается на сторонние файловые механизмы: Syncthing, Nextcloud, WebDAV или сетевые диски SMB/NFS. При одновременной записи базы двумя инженерами файловый транспорт создает конфликтный дубликат (database.sync-conflict-*.kdbx). Механизм слияния в KeePassXC опирается на встроенный анализатор истории объектов, но при нарушении блокировок ввода-вывода высок риск логического повреждения структуры заголовков KDBX-файла.

Расчет совокупной стоимости владения (TCO) и эксплуатационные риски

Для инженерной команды из 25 сотрудников экономика инфраструктуры демонстрирует критический разрыв между коммерческими SaaS-решениями и автономными инстансами:

[1Password Business]
25 инженеров * $7.99/мес = $199.75/мес
Итого прямой OPEX: $2 397.00 в год.
Скрытые риски: Зависимость от внешней инфраструктуры (Cloud Outage), блокировка аккаунта по комплаенсу, передача метаданных и графа связей на сторонние серверы.

[Официальный Bitwarden Self-Hosted]
Лицензия Enterprise: 25 инженеров * $4.00/мес = $100.00/мес ($1 200/год).
Инфраструктура: KVM VPS (4 vCPU, 8 GB RAM, 50 GB NVMe) ~ $25.00/мес ($300/год).
Итого: $1 500.00 в год + затраты на обслуживание сложного микросервисного стека.

[Vaultwarden Self-Hosted]
Лицензия: $0 (Open Source, AGPLv3, все функции организаций и коллекций разблокированы).
Инфраструктура: KVM VPS (1 vCPU, 1 GB RAM, 15 GB NVMe, резервное копирование) ~ $4.50/мес ($54/год).
Итого: $54.00 в год.

Vaultwarden исключает лицензионные платежи за доступ к общим папкам («Коллекциям») и двухфакторной аутентификации через FIDO2/WebAuthn, предоставляя полный корпоративный функционал официального Bitwarden на сервере минимальной конфигурации с годовой экономией свыше $2 300 в сравнении с 1Password. Эксплуатационный риск Vaultwarden заключается в отсутствии официальной поддержки со стороны вендора 8bit Solutions LLC и задержке в несколько дней при обратной разработке новых протокольных изменений, которые периодически вносятся в API upstream-клиентов.

Подготовка сервера Ubuntu 24.04: безопасность хоста, UFW файрвол и Docker Engine

Развертывание стека начинается с изоляции хостовой операционной системы. Для инстанса с 1 vCPU и 1 GB RAM под управлением Ubuntu 24.04 LTS (Noble Numbat) критически важно минимизировать потребление памяти системными сервисами, исключить вектор брутфорса учетных записей и устранить архитектурную уязвимость связки UFW и Docker, при которой Docker-демон по умолчанию публикует порты в обход правил пользовательского брандмауэра.

1. Первичная инициализация и создание непривилегированного пользователя

Работа под учетной записью root в постоянном режиме нарушает принцип наименьших привилегий (POLP) и создает риск компрометации хоста при выполнении скриптов развертывания. Подключитесь к свежеустановленному VPS по временному паролю или стандартному SSH-ключу провайдера:

# Обновление индекса пакетов и установленных компонентов ядра
export DEBIAN_FRONTEND=noninteractive
apt-get update && apt-get upgrade -y

# Создание сервисного пользователя без дефолтного пароля с командной оболочкой Bash
adduser --disabled-password --gecos "" deployer

# Включение пользователя в группу sudo для выполнения административных задач
usermod -aG sudo deployer

# Настройка входа по SSH-ключам для нового пользователя
mkdir -p /home/deployer/.ssh
chmod 700 /home/deployer/.ssh

# Копирование авторизованных ключей root (если на сервере уже настроен вход по ключу)
if [ -f /root/.ssh/authorized_keys ]; then
    install -m 600 /root/.ssh/authorized_keys /home/deployer/.ssh/authorized_keys
else
    # Вставьте ВАШ реальный публичный SSH-ключ рабочей станции
    echo "ssh-ed25519 ВАШ_ПУБЛИЧНЫЙ_КЛЮЧ user@workstation" > /home/deployer/.ssh/authorized_keys
    chmod 600 /home/deployer/.ssh/authorized_keys
fi

chown -R deployer:deployer /home/deployer/.ssh
[!WARNING] Ни в коем случае не отключайте парольную аутентификацию и не закрывайте текущую сессию терминала, пока не проверите успешный вход под пользователем deployer в соседней вкладке консоли (ssh deployer@IP_СЕРВЕРА). Обязательно замените строку ВАШ_ПУБЛИЧНЫЙ_КЛЮЧ на фактический открытый ключ вашей рабочей станции (содержимое файла ~/.ssh/id_ed25519.pub), иначе доступ к VPS будет утерян.

Для исключения задержек при вводе пароля при вызове команд автоматизации настройте бескомпромиссный sudoers-файл для созданного пользователя:

echo "deployer ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/90-deployer-init
chmod 0440 /etc/sudoers.d/90-deployer-init

2. Харденинг OpenSSH Daemon (OpenSSH 9.6p1)

В дистрибутиве Ubuntu 24.04 LTS управление демоном SSH переведено на systemd socket activation (ssh.socket). Конфигурация выполняется через добавление drop-in файлов в директорию /etc/ssh/sshd_config.d/, что сохраняет целостность базового пакета при обновлениях openssh-server.

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

cat << 'EOF' > /etc/ssh/sshd_config.d/99-vaultwarden-hardening.conf
# Отключение входа суперпользователя
PermitRootLogin no

# Полный запрет парольной и интерактивной PAM-аутентификации
PasswordAuthentication no
KbdInteractiveAuthentication no
AuthenticationMethods publickey

# Ограничение попыток авторизации до сброса TCP-сессии
MaxAuthTries 3
MaxSessions 2

# Тайм-ауты удержания сессии и сброс зависших соединений
ClientAliveInterval 300
ClientAliveCountMax 2

# Отключение небезопасных механизмов туннелирования
X11Forwarding no
AllowAgentForwarding no
AllowTcpForwarding yes

# Использование исключительно безопасных алгоритмов обмена ключами и шифрования
KexAlgorithms curve25519-sha256,[email protected],diffie-hellman-group16-sha512,diffie-hellman-group18-sha512
Ciphers [email protected],[email protected]
MACs [email protected]
EOF

Перед перезапуском службы обязательно выполните синтаксическую проверку созданного файла:

# Проверка валидности конфигурационных параметров
sshd -t

# При отсутствии ошибок перезапуск сокета OpenSSH
systemctl restart ssh.socket || systemctl restart ssh
Важно: Не разрывайте текущую сессию root до тех пор, пока в соседнем окне терминала не будет успешно выполнено тестовое подключение под пользователем deployer: ssh -i ~/.ssh/id_ed25519 deployer@<IP-АДРЕС_СЕРВЕРА>

3. Конфигурация сетевого экрана UFW и изоляция Docker-трафика

Сетевой фильтр UFW (Uncomplicated Firewall) является надстройкой над подсистемой ядра nftables/iptables. Политика хоста закрывает весь входящий трафик, пропуская исключительно соединения к портам управления и веб-интерфейса.

Примените базовые правила:

# Сброс базовых таблиц и установка политик по умолчанию
ufw --force reset
ufw default deny incoming
ufw default allow outgoing

# Открытие портов SSH, HTTP и HTTPS
ufw allow 22/tcp comment 'SSH Port'
ufw allow 80/tcp comment 'Let Encrypt ACME challenge'
ufw allow 443/tcp comment 'Reverse Proxy HTTPS'

# Активация брандмауэра
ufw --force enable
ufw status verbose

Архитектурная коллизия: Docker и обход правил UFW

Когда служба Docker запускает контейнер с директивой публикации портов формата -p 8080:80, демон манипулирует цепочкой PREROUTING в таблице nat и создает собственные правила в цепочке FORWARD пакетного фильтра Linux. Пакеты, идущие на порт 8080, маршрутизируются в контейнер в обход цепочки INPUT, где работают запрещающие правила UFW.

В производственном сценарии развертывания Vaultwarden на VPS прямое пробрасывание портов в глобальную сеть создает вектор прямой атаки на внутренние сервисы (например, панель администратора Vaultwarden или базу данных). Защита реализуется на уровне сетевой топологии: все контейнеры привязываются строго к локальному интерфейсу 127.0.0.1, а входящий внешний трафик с портов 80 и 443 принимает фронтенд-прокси (Nginx или Caddy), защищенный правилами UFW.

4. Установка официального Docker Engine и плагина Docker Compose

Дистрибутивные пакеты docker.io, поставляемые в штатных репозиториях Canonical, отстают от актуальных релизов Moby и содержат разрозненные версии пакета docker-compose. Для обеспечения стабильности и поддержки спецификаций Compose v2 установка производится напрямую из официального репозитория Docker Inc.

Удалите конфликтующие пакеты, если они присутствовали в базовом образе провайдера:

for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do 
    apt-get remove -y $pkg 2>/dev/null
done

Установите необходимые сертификаты и добавьте официальный GPG-ключ репозитория Docker:

apt-get update
apt-get install -y ca-certificates curl gnupg

# Создание каталога с безопасными правами для ключей пакетного менеджера
install -m 0755 -d /etc/apt/keyrings

# Загрузка и деарморинг официального ключа Docker
curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc

# Добавление репозитория под архитектуру хоста (amd64 / arm64)
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
  $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
  tee /etc/apt/sources.list.d/docker.list > /dev/null

apt-get update

Инсталлируйте производственный стек Docker Engine:

apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

Включение непривилегированного пользователя в группу docker исключает необходимость запуска sudo для вызова CLI:

usermod -aG docker deployer

5. Тюнинг демона Docker под VPS с ограниченным дисковым пространством

На VPS с диском объемом 15 GB NVMe некорректная конфигурация драйвера журналирования приводит к быстрому исчерпанию дискового пространства: стандартный лог-драйвер json-file не имеет ограничений по размеру файла и способен генерировать гигабайты текстовых логов при высокой интенсивности фоновых запросов.

Определите конфигурацию демона /etc/docker/daemon.json, задав лимиты на размер логов и включив изоляцию:

cat << 'EOF' > /etc/docker/daemon.json
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  },
  "live-restore": true,
  "userland-proxy": false,
  "no-new-privileges": true
}
EOF
  • max-size: 10m и max-file: 3: ограничивают дисковое пространство под логи каждого контейнера пределом в 30 МБ с автоматической ротацией.
  • live-restore: true: позволяет контейнерам продолжать выполнение при перезапуске или обновлении демона dockerd, минимизируя время простоя (Downtime).
  • userland-proxy: false: отключает компонент docker-proxy, освобождая 25–35 МБ оперативной памяти и передавая маршрутизацию трафика напрямую механизмам ядра Linux (iptables/nat hairpinning), что снижает latency p99 при трансляции внутренних сетевых пакетов.
  • no-new-privileges: true: глобально блокирует эскалацию привилегий внутри контейнеров через вызовы setuid и setgid.

Примените изменения и активируйте автозапуск служб через systemd:

systemctl daemon-reload
systemctl enable --now docker containerd
systemctl restart docker

Верифицируйте корректность развернутого окружения тестовой командой под пользователем deployer:

# Проверка версии и активности Docker Compose v2
docker compose version
# Проверка параметров ядра и активных подсистем cgroups
docker info --format '{{json .CgroupVersion}} | Storage: {{json .Driver}} | Logging: {{json .LoggingDriver}}'

Вывод должен подтвердить использование CgroupVersion: "2", драйвера хранилища overlay2 и сконфигурированного драйвера ротации логов. Базовый уровень безопасности хоста сформирован, порты закрыты от прямого сканирования, а среда исполнения готова к развертыванию контейнеризированного микросервиса.

Развертывание Vaultwarden в Docker Compose: переменные окружения и токены

После подготовки базовой конфигурации демона переходим к изоляции рабочего окружения приложения. Корректная установка и эксплуатация Vaultwarden на сервере требует четкого разделения конфигурационных каталогов, ограничения прав файловой системы на уровне POSIX ACL и изоляции контейнера от внешнего сетевого стека хоста. Аппаратная виртуализация KVM обеспечивает полноценную изоляцию подсистем cgroups v2 и пространств имен (namespaces), исключая утечку дескрипторов при пиковых нагрузках на подсистему ввода-вывода.

Иерархия каталогов и генерация хеша ADMIN_TOKEN

Создайте изолированную структуру каталогов в /opt/vaultwarden. Хранение данных монтируется в отдельный том vw-data, защищенный от чтения непривилегированными пользователями:

mkdir -p /opt/vaultwarden/data
chown -R 1000:1000 /opt/vaultwarden
chmod 700 /opt/vaultwarden/data
cd /opt/vaultwarden

Критическая ошибка большинства базовых инструкций — использование открытого текстового пароля в переменной ADMIN_TOKEN. Открытый токен доступен в выводе docker inspect, считывается процессами с доступом к /proc/$PID/environ и сохраняется в дампы системного журнала. Vaultwarden поддерживает валидацию токена панели администратора через криптографический хеш формата PHC (Password Hashing Competition) алгоритма Argon2id (RFC 9106).

Сгенерируйте хеш с аппаратными параметрами защиты от атак по таймингу и перебора на ASIC/GPU: стоимость памяти $m=65536$ (64 МиБ), вычислительная сложность $t=3$ итерации, параллелизм $p=4$ потока:

# Генерация хеша через встроенную утилиту официального бинарника
docker run --rm -it vaultwarden/server:latest /vaultwarden hash --preset owasp

Команда запросит ввод мастер-пароля администратора и вернет строку вида: $argon2id$v=19$m=65536,t=3,p=4$c29tZXNhbHQ...$abcdef123456...

Критический нюанс синтаксиса Docker Compose: Символ $ резервируется парсером Compose для интерполяции переменных окружения хоста. Если вставить полученный хеш в конфигурацию без экранирования, Compose расценит фрагменты $argon2id, $v=19, $m=65536, $t=3, $p=4 как неопределенные системные переменные и заменит их пустыми строками, сломав аутентификацию в /admin. Каждый знак доллара обязан быть экранирован вторым знаком $ (например: $$argon2id$$v=19...).

Формирование производственного файла окружения (.env)

Переменные конфигурации выносятся в файл .env. Это исключает случайную компрометацию секретов при фиксации манифестов в системах контроля версий Git.

Создайте файл /opt/vaultwarden/.env со строгими правами доступа:

cat << 'EOF' > /opt/vaultwarden/.env
# Основные сетевые и доменные параметры
DOMAIN=https://vault.example.com
DATA_FOLDER=/data
IP_HEADER=X-Forwarded-For

# Политики регистрации и доступа
SIGNUPS_ALLOWED=false
INVITATIONS_ALLOWED=true
SHOW_PASSWORD_HINT=false

# Хешированный токен панели управления (/admin)
# ВНИМАНИЕ: Знак $ экранирован двойным $$ для корректного чтения Docker Compose
ADMIN_TOKEN=$$argon2id$$v=19$$m=65536,t=3,p=4$$c29tZXNhbHQxMjM0NTY3OA$$WnhWbmlkZXJUZXN0SGFzaFN0cmluZ0ZvclZhdWx0d2FyZGVuMQ

# Сетевой транспорт и синхронизация
WEBSOCKET_ENABLED=true
ROCKET_WORKERS=10
ROCKET_PORT=80

# Оптимизация логирования
LOG_LEVEL=warn
EXTENDED_LOGGING=true
LOG_FILE=/data/vaultwarden.log
EOF

chmod 600 /opt/vaultwarden/.env

Назначение ключевых директив:

  • SIGNUPS_ALLOWED=false: полностью перекрывает маршрут публичной регистрации пользователей (/api/accounts/register). Защищает инстанс от бот-трафика, несанкционированного создания хранилищ и исчерпания дисковых инод (inode exhaustion).
  • INVITATIONS_ALLOWED=true: оставляет легитимный вектор онбординга новых учетных записей. Регистрация возможна исключительно по криптографическому одноразовому токену приглашения, отправляемому администратором через интерфейс организации.
  • WEBSOCKET_ENABLED=true: активирует внутренний сервис push-уведомлений на эндпоинте /notifications/hub. Браузерные расширения и мобильные клиенты переходят с постоянного HTTP Long-Polling опроса базы данных на единое двунаправленное WebSocket-соединение. Это ликвидирует паразитные TCP-хэндшейки, разгружает пул воркеров веб-сервера Rocket и снижает задержку синхронизации изменений паролей между десктопом и смартфоном до latency p99 < 15 мс.
  • IP_HEADER=X-Forwarded-For: предписывает парсить реальные IP-адреса клиентов из заголовка обратного прокси-сервера. Без этого параметра журнал авторизации будет фиксировать локальный IP-адрес 127.0.0.1, что сделает невозможным аудит безопасности и нарушит работу систем превентивной блокировки Fail2ban.
  • DOMAIN: фиксирует целевой FQDN. Параметр обязателен для генерации ссылок приглашений и корректной работы механизмов двухфакторной аутентификации WebAuthn/FIDO2, проверяющих Relying Party ID (rpId) в структуре клиентских ответов браузера.

Производственный манифест docker-compose.yml

Создайте файл /opt/vaultwarden/docker-compose.yml. Манифест привязывает прослушивание входящих портов исключительно к локальному интерфейсу 127.0.0.1, изолируя процесс от глобальной таблицы маршрутизации до момента настройки Nginx:

services:
  vaultwarden:
    image: vaultwarden/server:1.32.7-alpine
    container_name: vaultwarden
    restart: unless-stopped
    env_file:
      - .env
    volumes:
      - ./data:/data
    ports:
      - "127.0.0.1:8080:80"
    security_opt:
      - no-new-privileges:true
    cap_drop:
      - ALL
    cap_add:
      - CHOWN
      - SETUID
      - SETGID
      - DAC_OVERRIDE
    deploy:
      resources:
        limits:
          cpus: '1.50'
          memory: 512M
        reservations:
          cpus: '0.25'
          memory: 128M
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:80/alive"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 10s
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

Инженерные особенности конфигурации:

  1. Защита от обхода пакетного фильтра (127.0.0.1:8080:80): стандартная запись вида 8080:80 заставляет Docker вносить правила в цепочку PREROUTING фаервола iptables, пробрасывая трафик со всех внешних интерфейсов 0.0.0.0 в обход UFW/nftables. Явное связывание с 127.0.0.1 гарантирует, что контейнер будет доступен только локальным системным демонам.
  2. Лимитирование ресурсов ядра (cgroups v2): блок deploy.resources.limits фиксирует потолок потребления памяти в 512 МБ. Vaultwarden написан на Rust и в штатном режиме с SQLite укладывается в 30–60 МБ RAM. Ограничение исключает утечку памяти при попытке DoS-атаки тяжелыми криптографическими вызовами PBKDF2/Argon2 и предотвращает аварийное завершение критических служб хоста механизмом ядра Out-Of-Memory Killer.
  3. Минимизация привилегий ядра Linux: директива cap_drop: [ALL] лишает контейнер большинства возможностей суперпользователя (включая перехват сырых сокетов CAP_NET_RAW и манипуляции с модулями ядра), оставляя только базовые флаги POSIX, необходимые для переключения контекста пользователя внутри легковесного образа Alpine.

Запуск и валидация статуса контейнера

Запустите сервис и проверьте состояние изолированных сокетов:

# Инициализация и запуск в фоновом режиме
docker compose up -d

# Верификация статуса контейнера и встроенного Healthcheck
docker compose ps

Убедитесь, что контейнер находится в статусе Up (healthy). Проверьте, что порт 8080 не слушает внешний интерфейс хоста:

# Аудит открытых сокетов TCP
ss -tlpn | grep 8080

Корректный вывод утилиты ss обязан содержать 127.0.0.1:8080 в столбце локального адреса. Наличие 0.0.0.0:8080 или :::8080 свидетельствует об ошибке синтаксиса директивы ports и открывает сырой HTTP-трафик внешним сканерам сети.

Проверьте внутренний журнал инициализации хранилища SQLite и загрузки WebSocket-модуля:

docker compose logs -n 50 vaultwarden | grep -E "Rocket|WebSocket|Database"

Строки журнала должны подтвердить: * Инициализацию пула подключений к файловой базе данных SQLite в режиме WAL (Write-Ahead Logging), обеспечивающем устойчивость к крашам при параллельных дисковых операциях ввода-вывода (IOPS). * Успешную привязку сервиса WebSockets к рабочему порту. * Активацию защиты административной панели через зарегистрированный хеш-токен. Сервис готов к приему трафика через защищенный внешний реверс-прокси с терминацией TLS.

Настройка Nginx Reverse Proxy и автоматического SSL Let's Encrypt

Полноценная эксплуатация развернутого контейнера Vaultwarden технически невозможна по открытому протоколу HTTP. Это обусловлено не только рисками перехвата трафика открытого контура, но и жесткими архитектурными ограничениями современных веб-движков.

Архитектурная необходимость HTTPS: блокировка Web Crypto API

Клиентские приложения Vaultwarden (веб-интерфейс Web Vault, браузерные расширения Chromium/Firefox и десктопные клиенты) функционируют по принципу Zero-Knowledge Encryption. Вся криптографическая обработка данных — деривация ключей алгоритмами Argon2id или PBKDF2-HMAC-SHA256, а также симметричное шифрование/расшифровка хранилища по стандарту AES-CBC-256 / AES-GCM-256 — выполняется исключительно на стороне браузера пользователя до отправки полезной нагрузки в сокет.

Для выполнения этих примитивов стек фронтенда обращается к аппаратно ускоренному интерфейсу window.crypto.subtle (W3C Web Cryptography API). В соответствии со спецификацией W3C Secure Contexts, современные браузеры полностью отключают доступ к интерфейсу SubtleCrypto, если страница отдается через незащищенный транспорт HTTP (за исключением петлевого интерфейса localhost). При обращении к IP-адресу или домену ноды по незащищенному каналу флаг window.isSecureContext принимает значение false, свойство crypto.subtle возвращает undefined, и инициализация криптографического движка аварийно завершается: форма аутентификации блокируется, а расшифровка локального хранилища становится физически невозможной.

Терминация TLS на внешнем обратном прокси — обязательное функциональное требование для разблокировки криптографических интерфейсов клиента.

Установка зависимостей и подготовка сетевого периметра

Для терминации входящих HTTPS-соединений, балансировки вызовов и проброса бинарных фреймов WebSocket используется связка Nginx и Certbot. На нодах под управлением KVM с дистрибутивом Debian/Ubuntu установка пакетов выполняется штатными средствами:

apt update && apt install -y nginx certbot python3-certbot-nginx

Убедитесь, что фаервол хоста разрешает входящие TCP-сессии на портах 80 (HTTP для верификации ACME Challenge) и 443 (HTTPS):

# Для конфигураций с ufw
ufw allow 80/tcp
ufw allow 443/tcp
ufw reload

# Проверка прослушивания внешних интерфейсов демоном Nginx
ss -tlpn | grep -E ":(80|443)"

Конфигурация виртуального хоста Nginx (HTTP + WebSocket)

Начиная с версии Vaultwarden 1.29+ (переход на веб-фреймворк Rocket 0.5), обработка WebSockets полностью интегрирована в основной HTTP-порт сервиса (127.0.0.1:8080). Использование устаревшего отдельного порта 3012 упразднено: попытка проксировать трафик на порт 3012 приводит к ошибкам 502 Bad Gateway. И стандартные HTTP-запросы REST API, и событийно-ориентированные push-уведомления /notifications/hub обслуживаются единым апстримом с корректной передачей заголовков Upgrade.

Для исключения сброса долгих сессий и оптимизации сетевых буферов ядра создайте файл конфигурации хоста /etc/nginx/sites-available/vaultwarden.conf:

# Определение заголовка Upgrade для корректного переключения протокола на WebSocket
map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

upstream vaultwarden-backend {
    server 127.0.0.1:8080;
    keepalive 32;
}

server {
    listen 80;
    listen [::]:80;
    server_name vault.example.com;

    # Маршрут для выпуска и автоматического продления сертификатов Let's Encrypt
    location ^~ /.well-known/acme-challenge/ {
        root /var/www/html;
        default_type "text/plain";
        allow all;
    }

    location / {
        return 301 https://$host$request_uri;
    }
}

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name vault.example.com;

    # Пути к сертификатам (заполняются автоматически Certbot)
    # ssl_certificate /etc/letsencrypt/live/vault.example.com/fullchain.pem;
    # ssl_certificate_key /etc/letsencrypt/live/vault.example.com/privkey.pem;

    # Оптимизация TLS-сессий: кэширование снижает latency p99 при рукопожатиях
    ssl_session_timeout 1d;
    ssl_session_cache shared:MozSSL:10m;
    ssl_session_tickets off;

    # Протоколы и шифры Modern Profile (TLS 1.2 и TLS 1.3)
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256;
    ssl_prefer_server_ciphers off;

    # Заголовки безопасности (Hardening)
    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
    add_header X-Frame-Options "DENY" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "0" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https://haveibeenpwned.com; child-src 'self' https:; connect-src 'self' wss: https:; object-src 'none'" always;

    # Ограничение размера передаваемых вложений в хранилище (аватары, файлы баз)
    client_max_body_size 525M;
    client_body_buffer_size 128k;

    # Проксирование REST API и статики интерфейса
    location / {
        proxy_pass http://vaultwarden-backend;
        proxy_http_version 1.1;

        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-Port $server_port;

        proxy_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;

        proxy_buffering on;
        proxy_buffers 8 64k;
        proxy_buffer_size 128k;
    }

    # Маршрутизация полнодуплексного канала WebSockets
    location /notifications/hub {
        proxy_pass http://vaultwarden-backend;
        proxy_http_version 1.1;

        # Заголовки апгрейда HTTP-соединения до TCP-сокета WebSocket
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;

        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # Отключение буферизации для минимизации задержек доставки событий
        proxy_buffering off;

        # Увеличенный тайм-аут ожидания: предотвращает обрыв неактивных keep-alive сессий
        proxy_read_timeout 3600s;
        proxy_send_timeout 3600s;
    }
}

Активируйте созданный виртуальный хост через символическую ссылку и валидируйте конфигурационный файл на отсутствие синтаксических ошибок:

ln -s /etc/nginx/sites-available/vaultwarden.conf /etc/nginx/sites-enabled/
rm -f /etc/nginx/sites-enabled/default
nginx -t
systemctl reload nginx

Автоматический выпуск и валидация SSL Let's Encrypt через Certbot

Для выпуска бесплатных сертификатов используется ACME-клиент Certbot. В боевых инсталляциях предпочтительно использовать криптографию на эллиптических кривых (ECDSA с ключом secp384r1), так как она обеспечивает более высокую стойкость при меньшем размере ключа, снижая накладные расходы на вычисление подписи и уменьшая latency TLS-рукопожатия на 20–30 мс по сравнению со стандартным RSA-2048:

certbot --nginx \
  --agree-tos \
  --no-eff-email \
  --redirect \
  -m [email protected] \
  -d vault.example.com \
  --key-type ecdsa \
  --elliptic-curve secp384r1

Утилита автоматически модифицирует директивы ssl_certificate и ssl_certificate_key в блоке server и выполнит безопасный reload воркеров Nginx.

Для гарантии непрерывности сервиса проверяется системный таймер автоматического продления сертификатов:

# Проверка активности встроенного таймера systemd
systemctl status certbot.timer

# Тестовая имитация процесса перевыпуска (Dry-Run)
certbot renew --dry-run

Событие certbot.timer запускается дважды в сутки с рандомизированной задержкой, обновляя сертификаты при остаточном сроке действия менее 30 дней.

Валидация заголовков безопасности и сетевых сокетов

Завершающий шаг конфигурации обратного прокси — проверка корректности передачи директив безопасности через терминал с помощью утилиты curl:

curl -ILs https://vault.example.com | grep -E -i "Strict-Transport-Security|X-Frame-Options|X-Content-Type-Options|Content-Security-Policy"

Ожидаемый ответ сервера должен содержать строгие директивы защиты: * Strict-Transport-Security: max-age=63072000; includeSubDomains; preload — форсирует HSTS в браузере клиента на 2 года, исключая атаку с понижением протокола (SSL Stripping). * X-Frame-Options: DENY — аппаратно блокирует рендеринг веб-хранилища внутри элементов <iframe>, полностью нейтрализуя риск атак класса Clickjacking. * X-Content-Type-Options: nosniff — предотвращает MIME-sniffing исполняемых скриптов под видом статических ассетов или пользовательских иконок.

Проверьте статус активных внешних соединений к прокси-серверу:

ss -o state established '( dport = :443 or sport = :443 )'

Наличие установленных дескрипторов сокетов подтверждает готовность обратного прокси безопасно маршрутизировать трафик клиентов к бэкенду без риска утечки учетных данных.

Интеграция SMTP для отправки почты и двухфакторная аутентификация (2FA)

После стабилизации сетевого контура обратного прокси настраивается внутренняя подсистема уведомлений. Когда выполняется промышленное развертывание Vaultwarden на сервере, подключение внешнего транзакционного почтового шлюза является критическим условием эксплуатационной надежности, а не опциональной функцией. Архитектура Zero-Knowledge запрещает серверу восстанавливать мастер-пароли пользователей в открытом виде, поэтому почтовый канал выступает единственным доверенным внеполосным (out-of-band) транспортом для отправки верификационных токенов новых устройств (Device Verification), подтверждения приглашений в организации и обработки запросов аварийного доступа (Emergency Access) с принудительной временной задержкой отзыва.

Конфигурация системного почтового транспорта (SMTP)

Передача почты во встроенном микросервисе Vaultwarden на базе фреймворка Rocket конфигурируется через переменные окружения в файле .env или в блоке environment манифеста docker-compose.yml.

# Параметры подключения к внешнему релею / почтовому провайдеру
SMTP_HOST=smtp.mailprovider.com
SMTP_PORT=587
# Режимы безопасности: starttls (порт 587) или force_tls (порт 465)
SMTP_SECURITY=starttls
# Устаревшая переменная совместимости (дублирует logic-gate: true для 465/SSL)
SMTP_SSL=false
[email protected]
SMTP_PASSWORD=v4uLt_StR0ng_SmtP_p@ssw0rd!
[email protected]
SMTP_FROM_NAME=Vaultwarden Security Notifier

# Сетевые тайм-ауты и ограничения сокетов
SMTP_TIMEOUT=15
SMTP_AUTH_MECHANISM=Login

При работе с переменной SMTP_SECURITY учитывайте специфику криптографического рукопожатия: * starttls (дефолтный стандарт для порта 587) — клиент устанавливает незашифрованный TCP-сокет, отправляет команду EHLO, запрашивает расширение STARTTLS и только после этого выполняет TLS-хэндшейк с апгрейдом канала до защищенного. * force_tls (порт 465, SMTPS) — инициализирует сессию с немедленным шифрованием TLS до отправки любых прикладных SMTP-команд. Если в конфигурации активирован флаг SMTP_SSL=true, порт обязан быть переключен на 465, иначе сессия завершится ошибкой таймаута на этапе чтения баннера сервера.

На стороне хоста проверьте доступность исходящих сокетов. Большинство хостинг-провайдеров по умолчанию блокируют порт 25/TCP на уровне граничных файрволов для борьбы со спамом, однако порты 587/TCP и 465/TCP должны быть открыты:

# Диагностика прямого TCP-соединения с SMTP-сервером
nc -zv -w 5 smtp.mailprovider.com 587

# Валидация цепочки сертификатов почтового релея через OpenSSL
openssl s_client -starttls smtp -crlf -connect smtp.mailprovider.com:587 -brief

Примените изменения конфигурации контейнера с перезагрузкой окружения:

docker compose down && docker compose up -d

Проверьте отправку тестового сообщения через административную панель (https://vault.example.com/admin/diagnostics) или инспектируйте системный лог stdout демона Vaultwarden:

docker compose logs --tail=50 vaultwarden | grep -i smtp

Код 250 2.0.0 OK в ответе удаленного релея подтверждает корректную доставку транзакционных сообщений. При отсутствии почты новые клиенты не смогут пройти первичную валидацию отпечатка браузера: Vaultwarden генерирует криптографический токен устройства, записывает его хэш в базу данных и блокирует авторизацию до тех пор, пока пользователь не перейдет по одноразовой ссылке из письма.


Многофакторная защита периметра: TOTP, FIDO2 / WebAuthn и коды восстановления

Двухфакторная аутентификация в Vaultwarden перехватывает запрос на эндпоинте /identity/connect/token до того, как клиенту будет выдан мастер-ключ для расшифровки локального хранилища vault.json. Даже при компрометации мастер-пароля злоумышленник не получает доступ к криптографическому материалу симметричного ключа (AES-256-CBC / AES-256-GCM).

1. Синхронизация времени для TOTP (RFC 6238)

Временные одноразовые пароли (TOTP) рассчитываются как значение хэш-функции HMAC-SHA1 от текущего временного шага $T = \lfloor \frac{\text{unixtime}}{30} \rfloor$. Рассинхронизация системных часов хоста (Clock Skew) более чем на 30–60 секунд приводит к полной инвалидации генерируемых кодов во всех мобильных аутентификаторах (Aegis, Google Authenticator).

В KVM-гипервизорах виртуализация обеспечивает стабильный таймер kvm-clock, однако при кратковременном росте процессорного оверселлинга (%st в top более 5–10%) ядро может терять системные тики.

Убедитесь, что системный демон NTP активен и синхронизирует время хоста:

# Проверка статуса синхронизации через systemd-timesyncd
timedatectl status

# Принудительная активация демона сетевого времени
timedatectl set-ntp true

Если на сервере развернут chrony, выполните аудит дрейфа смещения системного генератора:

chronyc tracking | grep -E "Reference ID|System time|RMS offset"

Допустимое среднеквадратичное отклонение RMS offset должно находиться в пределах $\pm 0.005$ секунд.

2. Аппаратные ключи WebAuthn / FIDO2 (YubiKey, Passkeys)

Стандарт FIDO2 обеспечивает криптографическую защиту от фишинга за счет аппаратной привязки открытого ключа к доменному имени сервиса (origin binding).

Для корректной работы WebAuthn в Vaultwarden критически важно соблюдение следующих условий: 1. Запрос передается исключительно по защищенному протоколу HTTPS. Вызовы navigator.credentials.create() и navigator.credentials.get() аппаратно блокируются браузером в незащищенном контексте. 2. В файле .env переменная DOMAIN должна строго совпадать с внешним FQDN, передаваемым в заголовке Host от Nginx: env DOMAIN=https://vault.example.com Если протокол или порт отличаются (например, внутренний http://127.0.0.1:8080), браузерный API сгенерирует ошибку DOMException: The operation is insecure либо SecurityError: The relying party ID is not a valid domain string, исключая регистрацию токенов YubiKey 5 Series или биометрических пасскеев Touch ID/Windows Hello.

Активация выполняется пользователем через веб-интерфейс:
Настройки аккаунта -> Безопасность -> Двухфакторная аутентификация -> Ключи безопасности FIDO2 -> Управление.

3. Аварийный доступ и резервные коды восстановления (Break-Glass)

При первичной инициализации любого второго фактора Vaultwarden принудительно предлагает сгенерировать мастер-код восстановления (Recovery Code), состоящий из 16 шестнадцатеричных символов. Этот код обязан храниться на изолированном физическом носителе вне цифрового контура пользователя.

В случае утраты физического токена FIDO2 или деинициализации смартфона с TOTP, код восстановления позволяет сбросить флаг второго фактора на сервере без расшифровки данных.

Если утеряны и токен, и код восстановления, администратор сервера с привилегиями root на VPS может принудительно сбросить статус 2FA целевого пользователя через прямую манипуляцию с локальной базой данных SQLite:

# Остановка контейнера для предотвращения повреждения SQLite WAL-журнала
docker compose stop vaultwarden

# Деактивация двухфакторной аутентификации в базе данных
sqlite3 /vw-data/db.sqlite3 \
  "UPDATE users SET totp_secret = NULL, security_stamp = lower(hex(randomblob(16))) WHERE email = '[email protected]';" \
  "DELETE FROM twofactor WHERE user_uuid = (SELECT uuid FROM users WHERE email = '[email protected]');"

# Запуск контейнера
docker compose start vaultwarden

Модификация параметра security_stamp мгновенно инвалидирует все активные пользовательские токены авторизации (JWT) на всех рабочих станциях и смартфонах, принуждая пользователя авторизоваться заново по связке логина и мастер-пароля с последующей повторной привязкой новых 2FA-устройств.

Резервное копирование базы данных: режим SQLite WAL и безопасная выгрузка в S3

Штатная эксплуатация хранилища паролей требует строгого контроля за механизмом фиксации транзакций реляционного движка. В производственном окружении при настройке Vaultwarden на VPS базу данных необходимо перевести в режим упреждающей записи — Write-Ahead Logging (PRAGMA journal_mode=WAL;). На диске в каталоге данных формируется триада взаимосвязанных структур: основной файл db.sqlite3, циклический журнал транзакций db.sqlite3-wal и разделяемый индекс памяти db.sqlite3-shm.

Анатомия повреждения данных при наивном копировании (Cold vs Hot Copy)

Попытка зарезервировать базу данных стандартными утилитами копирования (cp, rsync, tar) при запущенном контейнере приводит к гарантированной скрытой деградации файловой структуры. В архитектуре WAL операции чтения не блокируют запись, а модифицированные страницы B-Tree сначала накапливаются в кольцевом буфере db.sqlite3-wal и параллельно индексируются в отображаемом через mmap() файле db.sqlite3-shm.

Утилита cp считывает блоки данных последовательными системными вызовами read() без захвата механизмов консультативной блокировки POSIX (fcntl с дескрипторами F_RDLCK / F_WRLCK). Если в момент чтения ядро Linux сбрасывает грязные страницы из page cache на блочное устройство или фоновый воркер SQLite выполняет контрольную точку (PRAGMA wal_checkpoint(PASSIVE);), утилита копирования считывает частично обновленные экстенты (эффект torn pages).

ПОТОК ЗАПИСИ (Vaultwarden):
[Транзакция A] ──> [db.sqlite3-wal] ──(fsync)──> [Обновление db.sqlite3-shm]
                                                         │
КОНКУРЕНТНЫЙ CP (Бэкап):                                 ▼
[read() chunk 0..4KB] ──> [read() chunk 4..8KB (TORN)] ──> Неконсистентный образ

В результирующем файле заголовочные метаданные страниц размером 4096 байт рассинхронизируются со смещениями узлов B-Tree. Изолированная копия db.sqlite3 без синхронного снимка db.sqlite3-wal теряет незафиксированные транзакции, а попытка восстановить работоспособность из такого файла завершается фатальным сбоем ядра: SQLITE_CORRUPT (11): database disk image is malformed.

Детерминированный горячий бэкап через SQLite Online Backup API

Корректное снятие снапшота без остановки контейнера и разрыва клиентских сессий реализуется исключительно через SQLite Online Backup API. Механизм блокирует распределение страниц, пошагово копирует согласованные блоки данных из основного тома и журнала WAL в целевой дескриптор, отслеживая модификации исходных страниц в реальном времени.

Команда выполняется непосредственно внутри пространства имен контейнера:

docker exec vaultwarden sqlite3 /data/db.sqlite3 ".backup '/tmp/backup.sqlite3'"

Для верификации структуры полученного слепка перед упаковкой выполняется диагностический опрос B-деревьев:

docker exec vaultwarden sqlite3 /tmp/backup.sqlite3 "PRAGMA integrity_check;"

Вывод обязан возвращать строго строку ok. Любой иной результат свидетельствует о сбое I/O на уровне гипервизора или повреждении оперативной памяти ноды.

Архитектура резервирования: асимметричный GPG и S3 Rclone

Полноценный архив Vaultwarden включает три независимых компонента: 1. Консистентный дамп базы (backup.sqlite3). 2. Директорию пользовательских шифрованных вложений (attachments/). 3. Приватный и публичный ключи инстанса (rsa_key.pem, rsa_key.pub, rsa_key.der), используемые для подписи сессионных JWT. Утрата этих ключей делает невозможной валидацию активных сессий на клиентских устройствах.

Для нейтрализации риска компрометации целевого объектного хранилища (AWS S3, Cloudflare R2 или Wasabi) данные шифруются по асимметричной схеме. На рабочем VPS размещается исключительно открытый ключ шифрования (vaultwarden-backup-public.asc). Закрытый ключ для расшифровки хранится изолированно на локальной рабочей станции инженера. При полной компрометации привилегий root на сервере злоумышленник технически не способен извлечь полезную нагрузку из исторических архивов.

1. Генерация и импорт асимметричной пары GPG-ключей

На вашей локальной доверенной рабочей станции (не на сервере!) сгенерируйте пару ключей и экспортируйте открытую часть:

# Генерация ключа на рабочей станции (RSA 4096)
gpg --batch --passphrase "НАДЕЖНЫЙ_СУПЕРПАРОЛЬ" --quick-generate-key "[email protected]" rsa4096 encr 0

# Экспорт публичного ключа для загрузки на сервер
gpg --armor --export "[email protected]" > vaultwarden-backup-public.asc

Скопируйте полученный файл vaultwarden-backup-public.asc на VPS в каталог /opt/vaultwarden/ и импортируйте его в связку ключей root:

# Импорт открытого ключа на сервере
gpg --import /opt/vaultwarden/vaultwarden-backup-public.asc

# Проверка наличия ключа в кольце (ключ готов для шифрования)
gpg --list-public-keys "[email protected]"

2. Подготовка конфигурации Rclone

Установите rclone и создайте конфигурационный файл /etc/rclone/rclone.conf:

[remote-s3]
type = s3
provider = Cloudflare
access_key_id = 9a8b7c6d5e4f3a2b1c0d
secret_access_key = 1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b
endpoint = https://<ACCOUNT_ID>.r2.cloudflarestorage.com
acl = private
no_check_bucket = true

3. Инженерный Bash-скрипт резервного копирования

Скрипт использует виртуальный диск в оперативной памяти (/dev/shm, tmpfs), предотвращая остаточную запись незашифрованных дампов на физический NVMe-накопитель хоста.

Создайте исполняемый файл /opt/vaultwarden/scripts/backup.sh:

#!/usr/bin/env bash
set -euo pipefail

# Конфигурационные параметры
DATA_DIR="/opt/vaultwarden/data"
CONTAINER_NAME="vaultwarden"
GPG_RECIPIENT="[email protected]"
S3_REMOTE="remote-s3:vaultwarden-backups"
TIMESTAMP="$(date -u +'%Y%m%d_%H%M%SZ')"
RETENTION_DAYS="30"

# Изоляция рабочей области в RAM (tmpfs)
TMP_DIR="$(mktemp -d -p /dev/shm vw_backup.XXXXXXXXXX)"
trap 'rm -rf "${TMP_DIR}"' EXIT

DB_SNAPSHOT="${TMP_DIR}/db.sqlite3"
TAR_PAYLOAD="${TMP_DIR}/vaultwarden_${TIMESTAMP}.tar"
ENCRYPTED_ARCHIVE="${TAR_PAYLOAD}.gpg"

# 1. Атомарное создание слепка базы через Online Backup API
docker exec "${CONTAINER_NAME}" sqlite3 /data/db.sqlite3 ".backup '/tmp/dump.sqlite3'"
docker cp "${CONTAINER_NAME}:/tmp/dump.sqlite3" "${DB_SNAPSHOT}"
docker exec "${CONTAINER_NAME}" rm -f /tmp/dump.sqlite3

# 2. Валидация консистентности страниц
INTEGRITY="$(sqlite3 "${DB_SNAPSHOT}" "PRAGMA integrity_check;")"
if [ "${INTEGRITY}" != "ok" ]; then
    echo "CRITICAL: Нарушена целостность B-Tree структуры: ${INTEGRITY}" >&2
    exit 1
fi

# 3. Упаковка бинарного дампа, криптографических ключей и вложений
tar -cf "${TAR_PAYLOAD}" \
    -C "${TMP_DIR}" "db.sqlite3" \
    -C "${DATA_DIR}" "rsa_key.pem" "rsa_key.pub" \
    -C "${DATA_DIR}" $([ -d "${DATA_DIR}/attachments" ] && echo "attachments" || true)

# 4. Асимметричное шифрование архива (AES-256 / RSA-4096)
gpg --batch --yes --trust-model always \
    --encrypt --recipient "${GPG_RECIPIENT}" \
    --output "${ENCRYPTED_ARCHIVE}" "${TAR_PAYLOAD}"

# 5. Синхронизация с S3-совместимым хранилищем и ротация
rclone copyto "${ENCRYPTED_ARCHIVE}" "${S3_REMOTE}/daily/vaultwarden_${TIMESTAMP}.tar.gpg" \
    --s3-upload-concurrency 4 \
    --s3-chunk-size 16M \
    --transfers 2

# Удаление слепков старше установленного периода хранения
rclone delete "${S3_REMOTE}/daily" --min-age "${RETENTION_DAYS}d"

Назначьте ограничительные права доступа к файлу:

chmod 700 /opt/vaultwarden/scripts/backup.sh
chown root:root /opt/vaultwarden/scripts/backup.sh

Автоматизация через изолированный Systemd Timer

Использование Systemd Timers вместо устаревшего демона cron обеспечивает жесткую изоляцию ресурсов через контрольные группы Linux (cgroups v2), сбор детальных метрик выполнения в journald и предотвращение деградации I/O производительности ноды.

Создайте юнит сервиса /etc/systemd/system/vaultwarden-backup.service:

[Unit]
Description=Vaultwarden Secure S3 WAL Backup
After=docker.service
Requires=docker.service

[Service]
Type=oneshot
ExecStart=/opt/vaultwarden/scripts/backup.sh
Nice=19
IOSchedulingClass=idle
CPUWeight=20
MemoryMax=512M
StandardOutput=journal
StandardError=journal

Создайте юнит таймера /etc/systemd/system/vaultwarden-backup.timer:

[Unit]
Description=Trigger Vaultwarden S3 Backup Daily

[Timer]
OnCalendar=*-*-* 03:00:00 UTC
RandomizedDelaySec=900
Persistent=true

[Install]
WantedBy=timers.target

Параметр RandomizedDelaySec=900 размывает окно запуска в интервале 15 минут, предотвращая пиковые нагрузки на сетевой интерфейс и дисковую подсистему VPS, а директива IOSchedulingClass=idle гарантирует, что утилизация канала ввода-вывода при архивации не приведет к росту метрик latency p99 и CPU Steal Time (%st) в основном контейнере хранилища.

Активируйте планировщик:

systemctl daemon-reload
systemctl enable --now vaultwarden-backup.timer

Проверьте статус инициализации расписания командой systemctl list-timers vaultwarden-backup.timer. Подсистема готова к непрерывной безаварийной репликации криптографических артефактов в геораспределенное хранилище.

Аварийное восстановление (Disaster Recovery): пошаговый регламент из S3

Резервная копия не имеет практической ценности до тех пор, пока процедура ее восстановления не будет полностью верифицирована на чистом инстансе (Disaster Recovery Drill). Ниже приведен боевой регламент восстановления при аппаратном сбое или миграции на новый VPS.

Шаг 1. Остановка контейнера и изоляция каталога данных

Разверните базовую структуру каталогов на целевом хосте и остановите контейнер во избежание конкурентных блокировок SQLite:

mkdir -p /opt/vaultwarden/data
cd /opt/vaultwarden
docker compose down

Шаг 2. Скачивание и расшифровка актуального слепка

Запросите список архивов из бакета через Rclone и скачайте последний консистентный слепок:

# Определение имени последнего созданного архива
LATEST_BACKUP=$(rclone lsf remote-s3:vaultwarden-backups/daily/ | sort | tail -n 1)
echo "Восстановление из архива: ${LATEST_BACKUP}"

# Загрузка зашифрованного архива во временную директорию
rclone copy "remote-s3:vaultwarden-backups/daily/${LATEST_BACKUP}" /tmp/

# Расшифровка архива приватным ключом GPG
gpg --decrypt "/tmp/${LATEST_BACKUP}" > /tmp/vaultwarden_restored.tar

Шаг 3. Распаковка артефактов и валидация B-Tree структуры

Извлеките дамп базы, RSA-ключи сессий JWT и директорию вложений:

# Распаковка файлов в рабочий каталог данных
tar -xvf /tmp/vaultwarden_restored.tar -C /opt/vaultwarden/data/

# Проверка целостности восстановленной базы данных
sqlite3 /opt/vaultwarden/data/db.sqlite3 "PRAGMA integrity_check;"

Вывод команды обязан возвращать строго строку ok.

Шаг 4. Корректировка POSIX-прав и перезапуск стека

Назначьте владельцем файлов пользователя контейнера (UID/GID 1000) и запустите сервисный стек:

# Выставление строгих прав POSIX
chown -R 1000:1000 /opt/vaultwarden/data
chmod 700 /opt/vaultwarden/data
chmod 600 /opt/vaultwarden/data/db.sqlite3* 2>/dev/null || true

# Фоновый запуск сервиса
docker compose up -d

# Мониторинг журнала инициализации
docker compose logs -f vaultwarden

После появления строк Rocket has launched from http://0.0.0.0:80 выполните контрольную синхронизацию через мобильное приложение или CLI-утилиту bw sync, подтверждая целостность парольного хранилища.

Чек-лист hardening-безопасности: защита панели администратора и Fail2ban

Когда завершено первичное развертывание инстанса Vaultwarden на VPS, сервис становится доступен во внешнем сетевом периметре. Ошибки конфигурирования reverse proxy и дефолтные таймауты оставляют вектор для атак методом перебора (credential stuffing, brute-force) и эксплуатации уязвимостей в зависимостях веб-сервера. Для вывода сервиса в production-эксплуатацию требуется реализация трехуровневого hardening-контура: изоляция административного интерфейса, интеграция сетевого фильтра с подсистемой netfilter ядра Linux и пайплайн контролируемых обновлений.


1. Сегментация точки входа /admin на уровне Nginx

Встроенная панель администратора Vaultwarden (/admin) защищается мастер-токеном (ADMIN_TOKEN). При компрометации или прямом переборе этого ключа злоумышленник получает доступ к дампу метаданных пользователей, списку организаций, отправке broadcast-уведомлений и принудительному удалению аккаунтов. Полагаться исключительно на энтропию токена недопустимо — эндпоинт обязан быть полностью скрыт от неавторизованного внешнего трафика.

Для этого в блоке server Nginx маршрут /admin изолируется директивами модуля ngx_http_access_module. Доступ разрешается исключительно доверенным IP-адресам: статическому адресу системного администратора, внутреннему офисному шлюзу или подсети VPN-оверлея (WireGuard / Tailscale).

Откройте конфигурационный файл виртуального хоста /etc/nginx/sites-available/vaultwarden.conf и внедрите изолированный блок location /admin:

# Ограничение частоты запросов для защиты от L7 DDoS и сканирования
limit_req_zone $binary_remote_addr zone=admin_limit:10m rate=3r/m;

server {
    listen 443 ssl http2;
    server_name vault.example.com;

    # Восстановление реального IP-адреса клиента за прокси/CDN (при наличии)
    # set_real_ip_from 10.0.0.0/8;
    # real_ip_header X-Forwarded-For;
    # real_ip_recursive on;

    # ... основные параметры SSL и заголовки безопасности ...

    # Изоляция административного интерфейса
    location /admin {
        # Принудительное ограничение по IP
        allow 192.0.2.45/32;          # Выделенный статический IP администратора
        allow 10.8.0.0/24;            # Внутренняя подсеть WireGuard VPN
        deny all;

        # Маскировка эндпоинта: возвращаем 404 вместо 403,
        # чтобы сканеры уязвимостей считали маршрут несуществующим
        error_page 403 =404 /index.html;

        limit_req zone=admin_limit burst=5 nodelay;

        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # Запрет кэширования административных сессий
        proxy_buffering off;
        add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0";
    }

    # Основной проксируемый маршрут хранилища
    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    # WebSocket для push-уведомлений
    location /notifications/hub {
        proxy_pass http://127.0.0.1:8080;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_buffering off;
    }
}

Проверьте валидность синтаксиса и перезагрузите конфигурацию без обрыва соединений:

nginx -t && systemctl reload nginx

Директива error_page 403 =404 /index.html пресекает попытки рекогносцировки: при обращении с любого неавторизованного IP веб-сервер отдает статус 404 Not Found, лишая злоумышленника подтверждения факта включения панели управления на сервере.


2. Защита от подбора паролей: Fail2ban и цепочка DOCKER-USER

Штатный механизм Docker манипулирует правилами iptables в обход стандартной цепочки INPUT, транслируя трафик напрямую в цепочки DOCKER и FORWARD. Попытка настроить классический джейл Fail2ban с действием iptables-multiport для контейнеризированного приложения приводит к критической ошибке: фильтр фиксирует инциденты, генерирует правила блокировки в цепочке INPUT, но трафик на порты 80 и 443 продолжает беспрепятственно поступать в контейнер.

Для корректной работы сетевого экрана правила обязаны инжектироваться в специальную пользовательскую цепочку DOCKER-USER. Это требует полноценной аппаратной виртуализации KVM, так как в контейнерных средах OpenVZ/LXC модификация низкоуровневых таблиц ядра netfilter и создание кастомных цепочек часто заблокированы на уровне хостового гипервизора.

Шаг 1: Активация структурированного логирования в Vaultwarden

Откройте /opt/vaultwarden/docker-compose.yml и убедитесь, что логи пишутся в файл внутри bind-монтируемой директории с детализацией IP-адресов клиентов:

services:
  vaultwarden:
    image: vaultwarden/server:latest
    container_name: vaultwarden
    restart: unless-stopped
    environment:
      - LOG_FILE=/data/vaultwarden.log
      - LOG_LEVEL=warn
      - EXTENDED_LOGGING=true
    volumes:
      - /opt/vaultwarden/data:/data
    ports:
      - "127.0.0.1:8080:80"

Примените изменения: docker compose up -d.

Шаг 2: Создание фильтра регулярных выражений Fail2ban

Создайте файл /etc/fail2ban/filter.d/vaultwarden.conf:

[Definition]
# Поиск событий неудачной аутентификации в API Vaultwarden
failregex = ^.*\[(?:vaultwarden::api::identity|api::identity)\]\[ERROR\] IP <HOST> failed to login.*$
            ^.*\[(?:vaultwarden::api::core::accounts|api::core::accounts)\]\[ERROR\] IP <HOST> failed 2FA.*$
            ^.*\[(?:vaultwarden::api::admin)\]\[ERROR\] Invalid admin token from <HOST>.*$

ignoreregex =

Проверьте корректность парсинга лога созданным фильтром с помощью встроенной утилиты:

fail2ban-regex /opt/vaultwarden/data/vaultwarden.log /etc/fail2ban/filter.d/vaultwarden.conf

Шаг 3: Настройка джейла и интеграция с цепочкой DOCKER-USER

Создайте конфигурацию джейла /etc/fail2ban/jail.d/vaultwarden.local:

[vaultwarden]
enabled  = true
port     = 80,443
filter   = vaultwarden
logpath  = /opt/vaultwarden/data/vaultwarden.log
maxretry = 4
findtime = 600
bantime  = 86400
# Использование цепочки DOCKER-USER для корректной маршрутизации пакетов
banaction = iptables-allports[name=vaultwarden, chain=DOCKER-USER]

Перезапустите сервис Fail2ban и проверьте активность джейла:

systemctl restart fail2ban
fail2ban-client status vaultwarden

Для верификации блокировки на уровне ядра выполните аудит цепочки netfilter:

iptables -L DOCKER-USER -n -v --line-numbers

Правила, вставленные Fail2ban, будут находиться в начале цепочки DOCKER-USER со счетчиками сброшенных пакетов (DROP), что полностью изолирует сетевой стек ноды от IP-адресов атакующих еще до момента попадания пакетов в сокеты веб-сервера.


3. Пайплайн контролируемого обновления контейнеров (docker compose pull)

Обновление Vaultwarden вслепую через автоматические сервисы (например, сторонние демоны вроде Watchtower) в production-средах категорически не рекомендуется. В случае релиза с критическими изменениями схемы базы данных SQLite или поломкой зависимостей контейнер уйдет в циклический рестарт (CrashLoopBackOff), заблокировав доступ пользователей к корпоративным секретам.

Обновление должно быть детерминированным: предварительный сброс WAL-журнала базы данных на диск, проверка целостности локального дампа, скачивание нового образа, атомарный перезапуск и валидация доступности сервиса через локальный эндпоинт /alive.

Создайте исполняемый maintenance-скрипт /opt/vaultwarden/scripts/update.sh:

#!/usr/bin/env bash
set -Eeuo pipefail

APP_DIR="/opt/vaultwarden"
DB_FILE="${APP_DIR}/data/db.sqlite3"
BACKUP_DIR="${APP_DIR}/data/pre_update_backups"
TIMESTAMP="$(date +%Y%m%d_%H%M%S)"

echo "[$(date -u)] Начало регламентного обновления Vaultwarden..."

# 1. Принудительный сброс WAL-журнала базы данных SQLite перед выкатыванием
if command -v sqlite3 >/dev/null 2>&1 && [ -f "${DB_FILE}" ]; then
    echo "[+] Контрольная точка WAL (TRUNCATE)..."
    sqlite3 "${DB_FILE}" 'PRAGMA wal_checkpoint(TRUNCATE);'
fi

# 2. Создание мгновенного локального страховочного снапшота
mkdir -p "${BACKUP_DIR}"
tar -czf "${BACKUP_DIR}/pre_update_${TIMESTAMP}.tar.gz" -C "${APP_DIR}" data

# Ротация локальных копий обновления: хранить 3 последних
find "${BACKUP_DIR}" -type f -name "pre_update_*.tar.gz" | sort -r | tail -n +4 | xargs -r rm -f

# 3. Скачивание актуального OCI-образа без остановки текущего контейнера
cd "${APP_DIR}"
echo "[+] Проверка наличия свежего слоя образа..."
docker compose pull vaultwarden

# 4. Пересоздание контейнера с минимизацией downtime (< 2 секунд)
echo "[+] Рестарт сервиса на обновленном базовом образе..."
docker compose up -d --remove-orphans vaultwarden

# 5. Валидация жизнеспособности сервиса (Liveness Probe)
echo "[+] Ожидание инициализации и проверка системных вызовов..."
sleep 5

HTTP_STATUS="$(curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:8080/alive || true)"

if [ "${HTTP_STATUS}" -eq 200 ]; then
    echo "[✓] Обновление успешно применено. Сервис функционирует штатно."
    # Очистка неиспользуемых устаревших образов для экономии дискового пространства VPS
    docker image prune -f
else
    echo "[!] КРИТИЧЕСКАЯ ОШИБКА: Сервис вернул HTTP ${HTTP_STATUS} вместо 200."
    echo "[!] Инициируйте аудит контейнера: docker compose logs --tail=100 vaultwarden"
    exit 1
fi

Сделайте файл исполняемым и зафиксируйте привилегии суперпользователя:

chmod 700 /opt/vaultwarden/scripts/update.sh
chown root:root /opt/vaultwarden/scripts/update.sh

Такой регламент гарантирует, что в процессе обновления не произойдет порчи схемы базы данных, время простоя сервиса составит доли секунды (downtime p99 < 2 с), а в случае непредвиденного сбоя у инженера останется готовый валидный снимок базы данных, зафиксированный строго перед накатом нового бинарного OCI-слоя.

Часто задаваемые вопросы (FAQ)

Совместим ли Vaultwarden со стандартными приложениями Bitwarden?

Да, Vaultwarden на 100% совместим со всеми официальными клиентами Bitwarden: расширениями для браузеров (Chrome, Firefox, Safari), десктопными приложениями (Windows, macOS, Linux), мобильными клиентами (iOS, Android) и CLI-утилитой. Достаточно в настройках клиента указать URL своего сервера.

Что произойдет при временном падении VPS — потеряю ли я доступ к паролям?

Нет. Все клиенты Bitwarden хранят зашифрованную локальную копию хранилища на вашем устройстве (смартфоне или ноутбуке). При отсутствии связи с сервером вы сможете открывать и копировать пароли в офлайн-режиме, а синхронизация произойдет автоматически при восстановлении доступа.

Безопаснее ли Vaultwarden, чем облачные 1Password или LastPass?

При корректной настройке (HTTPS, закрытая регистрация, 2FA и бэкапы) self-hosted Vaultwarden значительно безопаснее: база данных зашифрована на стороне клиента (Zero-Knowledge) и физически изолирована на вашем личном сервере, что исключает риск утечки в результате взлома централизованных облачных сервисов.

Как восстановить доступ, если забыт мастер-пароль?

Поскольку используется сквозное шифрование с нулевым разглашением (Zero-Knowledge Encryption), мастер-пароль не хранится на сервере даже в хешированном виде. Восстановить базу без мастер-пароля технически невозможно. Настоятельно рекомендуется настроить функцию 'Экстренный доступ' (Emergency Access) на доверенный контакт.