
Простой безопасный хранитель секретов
Keeper — это криптографическое хранилище секретов для Go. Он шифрует произвольные байтовые нагрузки в состоянии покоя, используя вывод ключа Argon2id и аутентифицированное шифрование XChaCha20-Poly1305 (по умолчанию), и сохраняет их во встроенной базе данных bbolt.
Он поставляется в виде трех компонентов, которые можно использовать независимо:
x/keephandler) — монтируйте конечные точки keeper на любой net/http mux одним вызовом, с подключаемыми хуками, защитами и кодировщиками ответов для контроля доступа и аудита.cmd/keeper) — терминальный интерфейс с постоянным REPL-сеансом, вводом секретов без эха и отсутствием истории в оболочке.Keeper был разработан как базовый уровень управления секретами для балансировщика нагрузки Agbero, но не имеет зависимостей от Agbero и работает в любом проекте Go.
Keeper разделяет секреты на сегменты (buckets). Каждый сегмент имеет неизменяемую политику BucketSecurityPolicy, которая определяет, как защищается его ключ шифрования данных (Data Encryption Key, DEK). Доступны четыре уровня.
Схема — это префикс URI, который группирует связанные сегменты (vault://, certs://, space:// или любое другое зарегистрированное имя). Уровень безопасности — это свойство политики сегмента, задаваемое при создании и неизменяемое впоследствии.
Вы можете свободно смешивать уровни безопасности в рамках одной схемы. Например, vault://system может быть LevelPasswordOnly (автоматически разблокируется при запуске), а vault://admin — LevelAdminWrapped (требует явного ввода учетных данных).
DEK сегмента выводится из мастер-ключа с помощью HKDF-SHA256 с разделенной по домену строкой информации для каждого сегмента (keeper-bucket-dek-v1:scheme:namespace). Все сегменты LevelPasswordOnly разблокируются автоматически при вызове UnlockDatabase с правильной мастер-парольной фразой. Во время выполнения не требуется учетных данных для каждого сегмента. Этот уровень подходит для секретов, которые процессу нужны при запуске без участия человека.
Сегмент имеет случайно сгенерированный 32-байтовый DEK, уникальный для этого сегмента. DEK никогда не хранится в открытом виде. Для каждого авторизованного администратора из HKDF(masterKey‖adminCred, dekSalt) выводится ключ шифрования ключей (KEK), который используется для оборачивания DEK с помощью XChaCha20-Poly1305. Сегмент недоступен до тех пор, пока администратор не вызовет UnlockBucket со своими учетными данными. Одна лишь мастер-парольная фраза не может расшифровать сегмент. Отзыв одного администратора не влияет на завернутую копию любого другого администратора.
DEK сегмента генерируется во время CreateBucket и сразу же оборачивается поставщиком HSMProvider, предоставленным вызывающей стороной. Поставщик выполняет операции оборачивания и разворачивания — keeper никогда не обрабатывает сырой DEK после передачи его поставщику. UnlockDatabase автоматически вызывает поставщика для разворачивания и начальной загрузки конверта для всех зарегистрированных HSM-сегментов. Ротация мастер-ключа не перешифровывает эти сегменты; DEK контролируется поставщиком.
Встроенная реализация SoftHSM, основанная на оберточном ключе, защищенном memguard, доступна в pkg/hsm для тестирования и CI-сред. Не используйте её в производстве.
Идентичен LevelHSM по поведению управления ключами, но HSMProvider реализуется через pkg/remote.Provider — настраиваемый HTTPS-адаптер, который делегирует оборачивание и разворачивание любому удаленному сервису KMS через TLS. В pkg/remote предоставлены готовые конфигурации для HashiCorp Vault Transit, AWS KMS и GCP Cloud KMS. Для производственного использования настройте TLSClientCert и TLSClientKey, чтобы включить взаимную TLS-аутентификацию.
salt ← random 32 bytes, generated once, stored as a versioned SaltStore (unencrypted) masterKey ← Argon2id(passphrase, salt, t=3, m=64 MiB, p=4) → 32 bytes
Хэш проверки сохраняется при первом выводе:```
verifyHash ← Argon2id(masterKey, "verification", t=1, m=64 MiB, p=4) → 32 bytes
Последующие вызовы DeriveMaster пересчитывают этот хэш и сравнивают его с crypto/subtle.ConstantTimeCompare. Несовпадение возвращает ErrInvalidPassphrase.
Соль KDF по замыслу хранится незашифрованной. Она должна быть читаемой до UnlockDatabase для получения мастер-ключа — шифрование её ключом, полученным из мастера, было бы циклическим. Соль KDF не является секретом; её цель — уникальность, а не конфиденциальность.
Каждое значение открытого текста шифруется с помощью XChaCha20-Poly1305 с использованием bucket DEK:``` nonce ← random 24 bytes ciphertext ← XChaCha20-Poly1305.Seal(nonce, DEK, plaintext)
### Вывод KEK — LevelAdminWrapped
Сохранённая запись представляет собой закодированную в msgpack структуру `Secret`, содержащую шифротекст, зашифрованные метаданные и версию схемы. Аутентификация неявная: расшифровка шифротекста неверным ключом приводит к ошибке аутентификации AEAD до того, как будет возвращён открытый текст.```
salt ← random 32 bytes, generated at bucket creation, stored in policy
ikm ← masterKey ‖ adminCredential
KEK ← HKDF-SHA256(ikm, salt, info="keeper-kek-v1") → 32 bytes
wrappedDEK ← XChaCha20-Poly1305.Seal(nonce, KEK, DEK)
Ключ KEK выводится с помощью HKDF, а не второго прохода Argon2. Мастер-ключ уже был получен с помощью дорогостоящего KDF; второй вызов Argon2 добавил бы сотни миллисекунд задержки к каждому вызову UnlockBucket без выгоды для безопасности. HKDF-SHA256 работает примерно за одну микросекунду.
Защита в глубину: Злоумышленник, скомпрометировавший только базу данных, получает упакованный DEK и соль HKDF, но не может вывести KEK без мастер-ключа. Злоумышленник, скомпрометировавший только мастер-ключ, не сможет распаковать ни один LevelAdminWrapped DEK без знания учётных данных администратора.
Секретные метаданные (время создания, время обновления, количество обращений, версия) шифруются отдельно от шифротекста:``` metaKey ← HKDF-SHA256(bucketDEK, nil, info="keeper-metadata-v1") → 32 bytes encryptedMeta ← XChaCha20-Poly1305.Seal(nonce, metaKey, msgpack(metadata))
Для `LevelAdminWrapped`, `LevelHSM` и `LevelRemote` это означает, что метаданные недоступны без учетных данных бакета, что предотвращает возможность злоумышленника, имеющего доступ на чтение к файлу базы данных, узнать шаблоны доступа или временные метки.
**Примечание о временных побочных каналах:** XChaCha20-Poly1305 обрабатывает полный шифротекст перед возвратом ошибки аутентификации. Путь резервного дешифрования (новый производный DEK → старый мастер-ключ как DEK) занимает одинаковое реальное время независимо от того, какой ключ успешен. Никакой временной побочный канал не раскрывает состояние миграции записи.
### Шифрование метаданных — политики, WAL и аудит