Skip to content
KitploitKITPLOIT
ИнструментыБлог
Отправить
ИнструментыБлог
Отправить

Инструменты для хакинга, пентеста и кибербезопасности — ваш арсенал защиты!

Kitploit — это каталог инструментов для хакинга, кибербезопасности и пентестинга. Находите последние обновления проектов для поиска уязвимостей, анализа систем, автоматизации тестирования и усиления вашей безопасности.

··Ленты·Контакты·Конфиденциальность·© 2026 Kitploit

Каталог инструментов

Категории

Все категории
Loading categories
keeper — Простой безопасный хранитель секретов | Kitploit
Инструменты/GitHubGitHub/agberohq/keeper
Аутентификация и авторизацияИнструменты шифрования/дешифрованияКриптографияОбнаружение Секретов
GitHubagberohq/keeper

keeper

Простой безопасный хранитель секретов

Репозиторий
12044 месяцев назадПроверено Kitploit

Популярное

Смотреть все →

Откройте для себя самые используемые инструменты нашего сообщества.

Изучить все инструменты

Просмотрите нашу коллекцию инструментов

Смотреть все инструменты →
Поделиться

keeper

Keeper — это криптографическое хранилище секретов для Go. Он шифрует произвольные байтовые нагрузки в состоянии покоя, используя вывод ключа Argon2id и аутентифицированное шифрование XChaCha20-Poly1305 (по умолчанию), и сохраняет их во встроенной базе данных bbolt.

Он поставляется в виде трех компонентов, которые можно использовать независимо:

  • Библиотека Go — встраивайте защищенное хранилище секретов прямо в ваш процесс с четырьмя уровнями безопасности, изоляцией DEK для каждого сегмента и защищенной от вмешательства цепочкой аудита.
  • HTTP-обработчик (x/keephandler) — монтируйте конечные точки keeper на любой net/http mux одним вызовом, с подключаемыми хуками, защитами и кодировщиками ответов для контроля доступа и аудита.
  • CLI (cmd/keeper) — терминальный интерфейс с постоянным REPL-сеансом, вводом секретов без эха и отсутствием истории в оболочке.

Keeper был разработан как базовый уровень управления секретами для балансировщика нагрузки Agbero, но не имеет зависимостей от Agbero и работает в любом проекте Go.


Содержание

  • Модель безопасности
  • Криптографическая архитектура
  • Иерархия ключей
  • Схема хранения
  • Цепочка аудита
  • Интеграция с Jack
  • x/keepcmd — переиспользуемые CLI-операции
  • x/keephandler — HTTP-обработчик
  • Справочник API
  • Каталог ошибок
  • Решения по безопасности
  • Зависимости

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

Keeper разделяет секреты на сегменты (buckets). Каждый сегмент имеет неизменяемую политику BucketSecurityPolicy, которая определяет, как защищается его ключ шифрования данных (Data Encryption Key, DEK). Доступны четыре уровня.

Схемы и уровни безопасности

Схема — это префикс URI, который группирует связанные сегменты (vault://, certs://, space:// или любое другое зарегистрированное имя). Уровень безопасности — это свойство политики сегмента, задаваемое при создании и неизменяемое впоследствии.

Вы можете свободно смешивать уровни безопасности в рамках одной схемы. Например, vault://system может быть LevelPasswordOnly (автоматически разблокируется при запуске), а vault://admin — LevelAdminWrapped (требует явного ввода учетных данных).

LevelPasswordOnly

DEK сегмента выводится из мастер-ключа с помощью HKDF-SHA256 с разделенной по домену строкой информации для каждого сегмента (keeper-bucket-dek-v1:scheme:namespace). Все сегменты LevelPasswordOnly разблокируются автоматически при вызове UnlockDatabase с правильной мастер-парольной фразой. Во время выполнения не требуется учетных данных для каждого сегмента. Этот уровень подходит для секретов, которые процессу нужны при запуске без участия человека.

LevelAdminWrapped

Сегмент имеет случайно сгенерированный 32-байтовый DEK, уникальный для этого сегмента. DEK никогда не хранится в открытом виде. Для каждого авторизованного администратора из HKDF(masterKey‖adminCred, dekSalt) выводится ключ шифрования ключей (KEK), который используется для оборачивания DEK с помощью XChaCha20-Poly1305. Сегмент недоступен до тех пор, пока администратор не вызовет UnlockBucket со своими учетными данными. Одна лишь мастер-парольная фраза не может расшифровать сегмент. Отзыв одного администратора не влияет на завернутую копию любого другого администратора.

LevelHSM

DEK сегмента генерируется во время CreateBucket и сразу же оборачивается поставщиком HSMProvider, предоставленным вызывающей стороной. Поставщик выполняет операции оборачивания и разворачивания — keeper никогда не обрабатывает сырой DEK после передачи его поставщику. UnlockDatabase автоматически вызывает поставщика для разворачивания и начальной загрузки конверта для всех зарегистрированных HSM-сегментов. Ротация мастер-ключа не перешифровывает эти сегменты; DEK контролируется поставщиком.

Встроенная реализация SoftHSM, основанная на оберточном ключе, защищенном memguard, доступна в pkg/hsm для тестирования и CI-сред. Не используйте её в производстве.

LevelRemote

Идентичен 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

root@kitploit:~
Хэш проверки сохраняется при первом выводе:```
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)

root@kitploit:~
### Вывод 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))

root@kitploit:~
Для `LevelAdminWrapped`, `LevelHSM` и `LevelRemote` это означает, что метаданные недоступны без учетных данных бакета, что предотвращает возможность злоумышленника, имеющего доступ на чтение к файлу базы данных, узнать шаблоны доступа или временные метки.

**Примечание о временных побочных каналах:** XChaCha20-Poly1305 обрабатывает полный шифротекст перед возвратом ошибки аутентификации. Путь резервного дешифрования (новый производный DEK → старый мастер-ключ как DEK) занимает одинаковое реальное время независимо от того, какой ключ успешен. Никакой временной побочный канал не раскрывает состояние миграции записи.

### Шифрование метаданных — политики, WAL и аудит

Все структурные метаданные также зашифрованы в состоянии покоя. Два ключа выводятся из мастер-ключа при `UnlockDatabase`:```
policyEncKey ← HKDF-SHA256(masterKey, nil, info="keeper-policy-enc-v1") → 32 bytes
auditEncKey  ← HKDF-SHA256(masterKey, nil, info="keeper-audit-enc-v1")  → 32 bytes

policyEncKey шифрует: значения BucketSecurityPolicy и WAL ротации.

auditEncKey шифрует: поля Scheme, Namespace и Details каждого события аудита.

Оба ключа очищаются из памяти при Lock(). Шифр, используемый для шифрования метаданных, — это тот же настраиваемый интерфейс crypt.Cipher, используемый для секретов — выбор пользователя (AES-256-GCM для FIPS, XChaCha20-Poly1305 по умолчанию) применяется автоматически.

Проводной формат для всех зашифрованных блобов метаданных:``` nonce (cipher.NonceSize() bytes) || AEAD-ciphertext

root@kitploit:~
### Хеширование ключей политики bucket

Ключи политики на диске представляют собой непрозрачные хеши, а не строки `scheme:namespace` в открытом виде, что предотвращает офлайн-перебор имен bucket:```
base ← hex(SHA-256("scheme:namespace"))[:32]   // 32 hex chars = 128-bit key space
_policies/<base>          → encrypted BucketSecurityPolicy
_policies/<base>__hash__  → SHA-256(encrypted policy bytes)
_policies/<base>__hmac__  → HMAC-SHA256(policyKey, encrypted policy bytes)

Постоянно находящийся в памяти schemeRegistry продолжает использовать "scheme:namespace" в качестве ключа — изменяется только представление на диске.

Аутентификация политик

Каждая запись политики содержит два тега целостности, записываемых атомарно в одной транзакции bbolt:``` hash ← SHA-256(encryptedPolicyBytes) — unauthenticated, pre-unlock integrity policyKey ← HKDF-SHA256(masterKey, nil, info="keeper-policy-hmac-v1") → 32 bytes hmac ← HMAC-SHA256(policyKey, encryptedPolicyBytes) — authenticated, post-unlock integrity

root@kitploit:~
До `UnlockDatabase` доступен только хэш SHA-256. После разблокировки `loadPolicy` проверяет тег HMAC. `UnlockDatabase` вызывает `upgradePolicyHMACs`, чтобы заполнить недостающие теги HMAC в политиках, созданных до появления этой функции.

### Аудит подписи HMAC```
auditKey ← HKDF-SHA256(masterKey, nil, info="keeper-audit-hmac-v1") → 32 bytes
HMAC ← HMAC-SHA256(auditKey, event fields including Seq)

Ключ подписи активируется при UnlockDatabase и очищается при Lock. При ротации мастер-ключа Rotate добавляет событие контрольной точки ротации ключей в каждую активную цепочку аудита, подписанное старым ключом аудита в качестве последнего события старой эпохи. История никогда не перезаписывается; контрольная точка является мостом доверия между эпохами.


Иерархия ключей```

passphrase │ └─ Argon2id(salt) ──→ masterKey (32 bytes, memguard Enclave) │ ├─ HKDF("keeper-audit-hmac-v1") ──→ auditKey (HMAC signing) ├─ HKDF("keeper-audit-enc-v1") ──→ auditEncKey (audit field encryption) ├─ HKDF("keeper-policy-hmac-v1") ──→ policyKey (policy HMAC) ├─ HKDF("keeper-policy-enc-v1") ──→ policyEncKey (policy/WAL encryption) │ ├─ [LevelPasswordOnly] │ └─ HKDF("keeper-bucket-dek-v1:scheme:ns") ──→ DEK │ └─ HKDF("keeper-metadata-v1") ──→ metaKey │ ├─ [LevelAdminWrapped] │ ├─ random 32 bytes ──→ DEK │ │ └─ HKDF("keeper-metadata-v1") ──→ metaKey │ │ │ └─ HKDF("keeper-kek-v1", masterKey‖adminCred, dekSalt) │ └─ KEK │ └─ XChaCha20-Poly1305(KEK, DEK) ──→ wrappedDEK │ └─ [LevelHSM / LevelRemote] ├─ random 32 bytes ──→ DEK │ └─ HKDF("keeper-metadata-v1") ──→ metaKey │ └─ HSMProvider.WrapDEK(DEK) ──→ wrappedDEK (stored; provider controls the wrapping key)

root@kitploit:~
Все промежуточные ключи обнуляются сразу после использования. Мастер-ключ никогда не записывается на диск в любом виде.

---

## Схема хранения

Базовая база данных — bbolt. Все bucket'ы и их содержимое:

| bbolt bucket | Ключ | Значение |
|---|---|---|
| `__meta__` | `salt` | msgpack — SaltStore (незашифровано; циклическая зависимость, если зашифровано) |
| `__meta__` | `verify` | raw bytes — Argon2id verification hash |
| `__meta__` | `rotation_wal` | `nonce‖AEAD(msgpack(RotationWAL))` |
| `__meta__` | `bucket_dek_done` | `"1"` — маркер завершения миграции DEK |
| `__policies__` | `hex(SHA-256(scheme:ns))[:32]` | `nonce‖AEAD(msgpack(BucketSecurityPolicy))` |
| `__policies__` | `<base>__hash__` | hex SHA-256 зашифрованных байтов политики |
| `__policies__` | `<base>__hmac__` | hex HMAC-SHA256(policyKey, encrypted policy bytes) |
| `__audit__/scheme/namespace` | event UUID | JSON — аудит Event |
| `__audit__/scheme/namespace` | `__chain_index__` | JSON — chainIndex |
| `scheme/namespace` | key string | msgpack — Secret struct |

### Secret struct (msgpack)```go
type Secret struct {
    Ciphertext    []byte `msgpack:"ct"`
    EncryptedMeta []byte `msgpack:"em,omitempty"`
    SchemaVersion int    `msgpack:"sv"`  // always 1
}

Поля события аудита

Структура Event использует отдельные поля маршрутизации в открытом виде (Scheme, Namespace) наряду с зашифрованными полями полезной нагрузки (EncScheme, EncNamespace, EncDetails). Контрольные суммы вычисляются по полям маршрутизации в открытом виде и зашифрованным байтам EncDetails, поэтому целостность цепочки можно проверить на трёх уровнях без какого-либо ключа:

УровеньИмеетМожет проверить
ПубличныйНичегоЦепочка контрольных сумм SHA-256 (обнаруживает подделку и вставку)
Владелец ключа аудитаauditEncKey

Пример: Сотрудник отдела комплаенса получает только auditEncKey. Он может проверить полную цепочку HMAC при смене ключей и прочитать все детали события, но не может расшифровать никакие секретные значения. Сторонний наблюдатель, имеющий только файл базы данных, всё ещё может обнаружить, было ли какое-либо событие изменено или вставлено после факта.

Версионированное хранилище соли

Соль KDF хранится в виде закодированной в msgpack структуры SaltStore под мета-ключом salt. Каждая ротация соли добавляет новую запись SaltEntry и увеличивает CurrentVersion. Старые записи сохраняются как аудиторский след. SaltStore хранится незашифрованным — см. Решения по безопасности.

WAL для атомарной ротации при сбоях

Rotate записывает WAL перед тем, как коснуться любой записи. WAL содержит WrappedOldKey: ключ до ротации, зашифрованный новым главным ключом. После сбоя старая парольная фраза исчезает; WrappedOldKey — единственный корректный способ перенести старый ключ через границу. При UnlockDatabase, если WAL присутствует, новый главный ключ расшифровывает WrappedOldKey, и ротация возобновляется с курсора WAL. Сам WAL шифруется ключом policyEncKey.


Цепочка аудита

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

Контрольная сумма. SHA-256 от prevChecksum, ID, BucketID, Scheme, Namespace, EncDetails, EventType и Timestamp. Использование Scheme/Namespace в открытом виде (всегда сохраняемых вместе с зашифрованными формами) гарантирует, что контрольная сумма стабильна при всех путях загрузки. EncDetails обеспечивает целостность зашифрованной полезной нагрузки.

HMAC. HMAC-SHA256 по всем полям, включая Seq. Злоумышленник, который может записывать в базу данных, но не знает ключа аудита, не может создать корректный HMAC. VerifyIntegrity проверяет оба уровня для каждого события.

Граница эпохи ротации ключей. При Rotate в каждую активную цепочку добавляется контрольное событие, несущее отпечатки как исходящего, так и входящего ключа аудита. Контрольное событие подписано исходящим ключом. Аудиторы, владеющие любым ключом эпохи, могут восстановить последующие ключи эпохи из поля wrapped_new_key и проверить непрерывность HMAC по всей цепочке.

Автоматическая очистка. Когда в Config установлен AuditPruneInterval, jack.Scheduler запускается периодически и вызывает PruneEvents для каждой зарегистрированной корзины. Корзины LevelHSM и LevelRemote никогда не очищаются, независимо от этой настройки.


Интеграция с Jack

Jack — опциональная библиотека для супервизии процессов. Если JackConfig предоставлен через WithJack, хранитель автоматически активирует фоновые компоненты:

  • Цикл автоматической блокировки: Периодически проверяет временную метку последней активности и сбрасывает DEK корзин LevelAdminWrapped после AutoLockInterval. Корзины LevelPasswordOnly остаются разблокированными, чтобы фоновые задания продолжались без перерыва. Паттерн с одной блокировкой на запись внутри задачи цикла устраняет состояние гонки RUnlock→Lock, присутствовавшее в более ранних реализациях.
  • Сборщик DEK для каждой корзины: Истечение срока жизни DEK для LevelAdminWrapped на основе TTL.
  • Пациенты мониторинга здоровья: проверка задержки чтения bbolt и верификация цикла шифрования/расшифровки, оба зарегистрированы в jack.Doctor.
  • Планировщик очистки аудита: Периодический вызов PruneEvents на всех корзинах, кроме HSM.
  • Асинхронный пул событий: События аудита отправляются без блокировки основной операции.

Если JackConfig не предоставлен, хранитель работает без этих фоновых задач. Хранитель никогда не вызывает pool.Shutdown — жизненный цикл пула принадлежит вызывающему коду.


x/keepcmd

x/keepcmd предоставляет переиспользуемые операции хранителя, независимые от какой-либо CLI-платформы. Встраивайте его в своё приложение, чтобы получить типизированное, тестируемое управление секретами без подключения двоичного файла CLI.```go import "github.com/agberohq/keeper/x/keepcmd"

cmds := &keepcmd.Commands{ Store: func() (*keeper.Keeper, error) { return security.KeeperOpen(cfg) // your own config }, Out: keepcmd.PlainOutput{}, NoClose: false, // true in REPL / session contexts }

cmds.List() // all keys: scheme://namespace/key cmds.List("vault") // all keys in scheme vault cmds.List("vault", "system") // all keys in vault://system cmds.Get("vault://system/jwt_secret") cmds.Set("vault://system/jwt_secret", "newsecret", keepcmd.SetOptions{}) cmds.Rotate(newPassphraseBytes) // caller resolved the passphrase — no prompter dependency cmds.RotateSalt(currentPassBytes) // same

root@kitploit:~
`keepcmd` никогда не вызывает `prompter` и не читает из stdin. Разрешение парольной фразы
полностью лежит на вызывающем коде — это обеспечивает безопасность пакета в
контекстах безголовых серверов.

`NoClose: true` предотвращает вызов `store.Close()` после каждой
операции. Используйте это в контекстах REPL/сессий, где одно хранилище используется
во множестве вызовов.

---

## x/keephandler

`x/keephandler` монтирует HTTP-эндпоинты keeper на любой `net/http` mux. Нет
внешней зависимости от маршрутизатора — он использует маршрутизацию по методу и шаблону из Go 1.22+ с
stdlib `http.ServeMux`.```go
import "github.com/agberohq/keeper/x/keephandler"

keephandler.Mount(mux, store,
    keephandler.WithPrefix("/api/keeper"),
    keephandler.WithGuard(func(w http.ResponseWriter, r *http.Request, route string) bool {
        if !acl.Allow(r.Header.Get("X-Principal"), route) {
            http.Error(w, `{"error":"forbidden"}`, http.StatusForbidden)
            return false
        }
        return true
    }),
    keephandler.WithHooks(
        keephandler.Hook{
            Route:       keephandler.RouteGet,
            CaptureBody: false,
            After: func(r *http.Request, status int, _ []byte) {
                audit.Log(r.Context(), route, status)
            },
        },
    ),
    keephandler.WithEncoder(func(w http.ResponseWriter, route string, status int, data any) {
        w.Header().Set("Content-Type", "application/json")
        w.WriteHeader(status)
        json.NewEncoder(w).Encode(map[string]any{
            "ok":    status < 400,
            "route": route,
            "data":  data,
        })
    }),
    keephandler.WithRoutes(func(m *http.ServeMux) {
        m.HandleFunc("POST /api/keeper/totp/{user}", myTOTPHandler)
    }),
)

Эндпоинты

Контракт хука

BeforeFunc возвращает (allow bool, err error).

  • (true, nil) — разрешить выполнение запроса.
  • (false, nil) — прервать; хук уже записал полный ответ.
  • (false, err) — прервать; фреймворк записывает 500 с помощью err.Error(). Хук не должен ничего записать в w.

Hook.CaptureBody bool управляет тем, получает ли AfterFunc тело ответа. false (по умолчанию) стоит одного легковесного обёртки statusWriter; true буферизирует полное тело в bytes.Buffer для AfterFunc — одно выделение памяти на запрос.

Хуки выполняются в порядке регистрации. Несколько вызовов WithHooks суммируются. Используется только первый хук, зарегистрированный для данного имени маршрута — последующие регистрации для того же маршрута игнорируются.


Справочник API

Создание и разблокировка```go

store, err := keeper.New(keeper.Config{ DBPath: "/var/lib/agbero/keeper.db", AutoLockInterval: 30 * time.Minute, EnableAudit: true, AuditPruneInterval: 24 * time.Hour, AuditPruneKeepLastN: 10_000, AuditPruneOlderThan: 90 * 24 * time.Hour, DBLatencyThreshold: 200 * time.Millisecond, Logger: logger, }, keeper.WithJack(keeper.JackConfig{ Pool: jackPool, Shutdown: jackShutdown, })) defer store.Close()

// Shorthand (wraps DeriveMaster + UnlockDatabase): if err := store.Unlock([]byte(os.Getenv("KEEPER_PASSPHRASE"))); err != nil { log.Fatal(err) // ErrInvalidPassphrase on wrong passphrase }

root@kitploit:~
`UnlockDatabase` выполняет следующее по порядку:

1. Выводит и активирует ключ подписи audit HMAC
2. Выводит и активирует ключ policy HMAC
3. Выводит и активирует `policyEncKey` и `auditEncKey`
4. Очищает и перезагружает `schemeRegistry` (расшифровывает все blob-объекты политик)
5. Возобновляет любую прерванную ротацию WAL
6. Обновляет теги policy HMAC
7. Загружает все DEK корзины `LevelPasswordOnly` в Envelope
8. Запускает фоновые задачи (цикл миграции, автоматическая блокировка, health patients)

### Корзина LevelPasswordOnly — полный жизненный цикл```go
err := store.CreateBucket("vault", "system", keeper.LevelPasswordOnly, "init")

store.Set("vault://system/jwt_secret", []byte("supersecret"))
val, err := store.Get("vault://system/jwt_secret")

// Namespaced convenience wrappers
store.SetNamespaced("admin", "jwt_secret", secretBytes)
val, err = store.GetNamespaced("admin", "jwt_secret")

LevelAdminWrapped bucket — полный жизненный цикл```go

err := store.CreateBucket("finance", "payroll", keeper.LevelAdminWrapped, "ops-team") err = store.AddAdminToPolicy("finance", "payroll", "alice", []byte("alicepass"))

store.SetNamespacedFull("finance", "payroll", "salary_key", []byte("AES256..."))

store.LockBucket("finance", "payroll") err = store.UnlockBucket("finance", "payroll", "bob", []byte("bobpass")) // ErrAuthFailed — does not distinguish wrong password from unknown admin (CWE-204)

err = store.RevokeAdmin("finance", "payroll", "alice") err = store.RotateAdminWrappedDEK("finance", "payroll", "bob", []byte("bobpass"))

needs, err := store.NeedsAdminRekey("finance", "payroll")

root@kitploit:~
### LevelHSM / LevelRemote бакеты```go
import (
    "github.com/agberohq/keeper/pkg/hsm"
    "github.com/agberohq/keeper/pkg/remote"
)

// SoftHSM — testing only
provider, _ := hsm.NewSoftHSM()
store.RegisterHSMProvider("secure", "keys", provider)
store.CreateBucket("secure", "keys", keeper.LevelHSM, "ops")

// Vault Transit
cfg := remote.VaultTransit("https://vault.corp:8200", vaultToken, "my-key")
cfg.TLSClientCert = "/etc/keeper/client.crt"
cfg.TLSClientKey  = "/etc/keeper/client.key"
provider, _ = remote.New(cfg)
store.RegisterHSMProvider("tenant", "secrets", provider)
store.CreateBucket("tenant", "secrets", keeper.LevelRemote, "ops")

Экспорт аудиторского ключа```go

// Export the audit encryption key to allow a third-party auditor to decrypt // event details without access to the master passphrase. auditKey, err := store.ExportAuditKey() defer zero.Bytes(auditKey)

events, err := auditStore.LoadChain("vault", "system", auditKey)

root@kitploit:~
### Ротация ключей```go
// Rotate passphrase — crash-safe WAL, resumes on next Unlock if interrupted
store.Rotate([]byte("new-passphrase"))

// Rotate KDF salt — re-derives master key, re-encrypts LevelPasswordOnly
store.RotateSalt([]byte("current-passphrase"))

Сравнение-и-обмен```go

err := store.CompareAndSwapNamespacedFull("vault", "system", "counter", []byte("old"), []byte("new")) // ErrCASConflict if current value does not match old

root@kitploit:~
### Резервное копирование```go
f, _ := os.Create("keeper.db.bak")
info, err := store.Backup(f)
// info.Bytes, info.Timestamp, info.DBPath

Каталог ошибок

Все sentinel-ошибки работают с errors.Is и errors.As. Стек вызовов захватывается в момент создания с помощью github.com/olekukonko/errors.


Решения по безопасности

ErrAuthFailed объединяет все сбои UnlockBucket (CWE-204 / CVSS 5.3). Как неизвестный ID администратора, так и неверный пароль возвращают ErrAuthFailed. Это предотвращает перечисление ID администратора по времени или сравнению строк ошибок. RevokeAdmin сохраняет ErrAdminNotFound, поскольку это административная операция в уже разблокированном хранилище. Сравнение за константное время для проверки наличия ID администратора намеренно опущено. Злоумышленник, способный измерить субмикросекундные различия в поиске по bucket-ам bbolt, должен иметь доступ к локальной файловой системе — после чего он может напрямую прочитать bucket политик. Модель угроз предполагает, что файл базы данных может быть скомпрометирован; защита от удалённого перечисления по времени является основной задачей.

Argon2id доминирует во временных характеристиках. Argon2id занимает 200–500 мс на типовом оборудовании. Различия после вычисления на несколько порядков меньше и не могут быть измерены удалённо. Никакого искусственного выравнивания не применяется.

DEK извлекается внутри границ транзакции CAS. CompareAndSwapNamespacedFull извлекает DEK bucket внутри транзакции записи bbolt, устраняя окно, в котором одновременный вызов Rotate мог бы изменить DEK между извлечением и использованием.

Парольная фраза никогда не хранится как строка Go в HTTP-обработчике. Все три поля парольной фразы (passphrase, new_passphrase) декодируются из JSON непосредственно в []byte через извлечение сырой карты, сохраняя массив строки вне долгоживущей кучи. Копия []byte затирается с помощью wipeBytes после использования.

В CLI нет флага --passphrase. Флаги видны в выводе ps и истории команд оболочки. CLI принимает парольную фразу только из переменной KEEPER_PASSPHRASE или интерактивного приглашения без эха.

Секретные значения в REPL никогда не видны. set <key> в REPL без встроенного значения использует term.ReadPassword — оно не отображается в прокрутке терминала, истории команд или ps. Встроенное значение (set key value) может быть передано для нечувствительных данных, если это удобно.

SaltStore намеренно не зашифровано. Соль KDF должна быть читаема до UnlockDatabase для вывода мастер-ключа. policyEncKey (используемый для шифрования всех остальных метаданных) сам выводится из мастер-ключа — шифрование соли с помощью policyEncKey было бы циклическим. Соль KDF обеспечивает уникальность, а не конфиденциальность; нет никакой ценности безопасности в её шифровании.

Ключи bucket-ов политик хэшируются, а не хранятся в открытом виде. Ключи политик на диске — это hex(SHA-256("схема:пространство_имён"))[:32] — 128 бит ключевого пространства — а не читаемые строки. Офлайн-злоумышленник, читающий файл bbolt, не сможет перечислить имена bucket-ов без расшифровки blob-ов политик.

Шифрование метаданных использует тот же интерфейс шифрования, что и секреты. Все операции с policyEncKey и auditEncKey проходят через s.config.NewCipher(key) — тот же интерфейс crypt.Cipher, настроенный для секретных значений. Выбор пользователем шифра (AES-256-GCM для FIPS 140, XChaCha20-Poly1305 по умолчанию) автоматически применяется к шифрованию политик, WAL и аудита. Ни один путь кода не содержит жёстко заданный алгоритм.

Bucket-ы LevelHSM и LevelRemote пропускаются во время ротации мастер-ключа. reencryptAllWithKey и RotateSalt явно пропускают эти bucket-ы. DEK управляется провайдером; ротация мастер-соли на него не влияет.

Безопасная при сбоях ротация с WrappedOldKey. Rotate записывает WAL перед любыми изменениями записей. WAL содержит WrappedOldKey: мастер-ключ до ротации, зашифрованный новым мастер-ключом. После сбоя UnlockDatabase расшифровывает WrappedOldKey, используя проверенный новый ключ, и возобновляет ротацию с курсора.


Зависимости

Скачать инструмент
Полная цепочка + расшифровка Scheme/Namespace/Details
ОператорГлавная парольная фразаВсё
МетодПутьОписание
POST{prefix}/unlockРазблокировать хранилище с помощью парольной фразы
POST{prefix}/lockЗаблокировать хранилище
GET{prefix}/statusСостояние блокировки — безопасно опрашивать без аутентификации
GET{prefix}/keysСписок всех секретных ключей
GET{prefix}/keys/{key}Получить секретное значение
POST{prefix}/keysСохранить секрет (JSON или multipart)
DELETE{prefix}/keys/{key}Удалить секрет
POST{prefix}/rotateСменить мастер-пароль
POST{prefix}/rotate/saltСменить соль KDF
GET{prefix}/backupПотоковая передача снимка базы данных
ОшибкаЗначение
ErrStoreLockedПопытка выполнения операции, пока хранилище заблокировано
ErrInvalidPassphraseНеверная мастер-парольная фраза
ErrAuthFailedЛюбой сбой UnlockBucket — не различает неверный пароль и неизвестный ID администратора (CWE-204)
ErrKeyNotFoundСекретный ключ не существует
ErrBucketLockedBucket не был разблокирован
ErrPolicyImmutableВторая политика для существующего bucket
ErrPolicyNotFoundНет политики для данной схемы/пространства имён
ErrAdminNotFoundID администратора отсутствует в политике — только для RevokeAdmin
ErrHSMProviderNilHSM/Remote bucket создан без зарегистрированного провайдера
ErrCheckLatencyЗадержка чтения БД превысила DBLatencyThreshold
ErrCASConflictТекущее значение не совпадает с ожидаемым в CompareAndSwap
ErrSecurityDowngradeПеремещение между bucket-ами с более высокого уровня безопасности на более низкий
ErrAlreadyUnlockedUnlockDatabase вызван для уже разблокированного хранилища
ErrMasterRequiredUnlockDatabase вызван с nil или уничтоженным Master
ErrChainBrokenСбой проверки целостности цепочки аудита
ErrMetadataDecryptНе удалось расшифровать зашифрованные метаданные
ErrPolicySignatureСбой проверки HMAC политики — запись была подделана
ПакетНазначение
go.etcd.io/bboltВстраиваемое хранилище ключ-значение
golang.org/x/cryptoArgon2id, XChaCha20-Poly1305, HKDF, scrypt
github.com/awnumar/memguardБезопасный анклав ключей в памяти (мастер-ключ, DEK)
github.com/vmihailenco/msgpack/v5Бинарная сериализация секретов и политик
github.com/olekukonko/jackСупервизор процессов (опциональная интеграция Jack)
github.com/olekukonko/llСтруктурированное логирование
github.com/olekukonko/errorsSentinel-ошибки со стеком вызовов
github.com/olekukonko/zeroБезопасное зануление байтовых срезов
github.com/olekukonko/prompterПриглашения терминала без эха (только CLI)
github.com/integrii/flaggyРазбор флагов CLI (только cmd/keeper)
golang.org/x/termОпределение TTY и чтение пароля без эха (только CLI)