Хранилище (BYOB)
Статус: Доступно с 2026-04-22.
Раздел Хранилище позволяет тенанту подключить собственный объектный сторадж (S3-бакет, S3-совместимый бакет, GCS-бакет или Azure Blob-контейнер) и хранить документы аппликантов / KYB-материалы / селфи в нём, а не в общем хранилище OneKYC. Эта возможность называется BYOB — Bring Your Own Bucket.
По умолчанию все тенанты используют общее хранилище OneKYC (режим shared). Подключение собственного бакета — опциональная функция, доступная без миграции данных и без даунтайма.
Когда использовать BYOB
Подключение собственного бакета имеет смысл в следующих случаях:
- Соответствие требованиям регуляторов / data residency. Документы пользователей должны храниться в конкретной юрисдикции или в инфраструктуре, контролируемой тенантом.
- Корпоративные политики безопасности. Доступ к данным должен ограничиваться силами команды безопасности тенанта (own KMS keys, own IAM, own audit trail в CloudTrail / Cloud Audit Logs / Storage Analytics).
- Контроль расходов. Тенант оплачивает хранение напрямую своему облачному провайдеру.
- Повышенный контроль за жизненным циклом. Самостоятельное управление lifecycle-политиками (archival → Glacier / Coldline / Cool tier, retention).
Если ни один из пунктов не критичен, рекомендуется оставить общее хранилище — оно требует нулевой настройки и обслуживается командой OneKYC.
Поддерживаемые провайдеры
| Провайдер | Значение provider | Аутентификация | Особенности |
|---|---|---|---|
| AWS S3 | aws_s3 | STS AssumeRole + ExternalID | Cross-account роль; OneKYC не хранит долговременные ключи AWS |
| S3-совместимый | s3_compat | Static AccessKey + SecretKey | Cloudflare R2, Backblaze B2, MinIO, Wasabi, IBM Cloud Object Storage |
| Google Cloud Storage | gcs | Service Account JSON | base64-encoded ключ Service Account |
| Azure Blob Storage | azure_blob | Account Key, ConnectionString или SAS Token | Любой из трёх способов |
Для AWS S3 используется паттерн AssumeRole + ExternalID — рекомендованный AWS способ предоставления стороннему сервису ограниченного доступа к ресурсам аккаунта без долговременных credentials. ExternalID защищает от атаки «confused deputy».
Создание конфигурации
Через админ-панель
- Перейдите в Настройки → Хранилище.
- Нажмите «Подключить собственный бакет».
- Выберите тип провайдера и заполните параметры (см. ниже).
- Сохраните черновик — конфигурация создаётся в статусе
pendingи пока не используется. - Запустите валидацию (см. раздел «Проверка конфигурации»).
- После успешной валидации — активируйте конфигурацию.
Через API
Пример для AWS S3
POST /v1/admin/storage/configs HTTP/1.1
Authorization: Bearer <admin_jwt>
Content-Type: application/json
{
"provider": "aws_s3",
"bucket": "my-tenant-onekyc",
"region": "eu-central-1",
"aws_role_arn": "arn:aws:iam::123456789012:role/OneKYCStorage",
"sse_mode": "sse_kms",
"sse_kms_key_id": "arn:aws:kms:eu-central-1:123456789012:key/abcd-..."
}В ответе вернётся объект конфигурации со статусом pending и сгенерированный aws_external_id. Этот ExternalID необходимо добавить в условие trust policy IAM-роли на стороне тенанта (см. ниже).
Пример для S3-совместимого хранилища (Cloudflare R2)
POST /v1/admin/storage/configs HTTP/1.1
{
"provider": "s3_compat",
"bucket": "tenant-onekyc-r2",
"region": "auto",
"endpoint": "https://<account-id>.r2.cloudflarestorage.com",
"use_path_style": true,
"access_key_id": "<R2 access key id>",
"secret_key": "<R2 secret>"
}Пример для Google Cloud Storage
POST /v1/admin/storage/configs HTTP/1.1
{
"provider": "gcs",
"bucket": "tenant-onekyc-gcs",
"service_account_json": "<base64-encoded SA JSON key file>"
}Пример для Azure Blob
POST /v1/admin/storage/configs HTTP/1.1
{
"provider": "azure_blob",
"bucket": "onekyc-data",
"endpoint": "<storage account name>",
"azure_account_key": "<account key>"
}Поля
secret_key,service_account_json,azure_account_key,azure_connection_string,azure_sas_tokenявляются write-only — они шифруются и никогда не возвращаются ни одним из эндпоинтов.
Требуемые IAM-разрешения (AWS)
Trust policy IAM-роли (на стороне тенанта):
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": { "AWS": "arn:aws:iam::<onekyc account id>:role/onekyc-byob-resolver" },
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": { "sts:ExternalId": "<aws_external_id из ответа Create>" }
}
}]
}Permission policy IAM-роли:
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:GetObject",
"s3:DeleteObject",
"s3:GetObjectAcl",
"s3:PutObjectAcl"
],
"Resource": "arn:aws:s3:::<bucket>/*"
}, {
"Effect": "Allow",
"Action": [
"s3:ListBucket",
"s3:GetBucketLocation"
],
"Resource": "arn:aws:s3:::<bucket>"
}]
}Если включён режим SSE-KMS (sse_mode = "sse_kms"), также потребуется политика на сам KMS-ключ:
{
"Effect": "Allow",
"Action": [
"kms:Encrypt",
"kms:Decrypt",
"kms:GenerateDataKey"
],
"Resource": "<sse_kms_key_id>"
}Проверка конфигурации (Validation Probe)
Перед активацией конфигурация проходит автоматический набор из 14 проверок (10 базовых + 4 production-readiness):
POST /v1/admin/storage/configs/{id}/validate| # | Проверка | Что проверяет |
|---|---|---|
| 1 | head_bucket | Бакет существует и предоставлены права s3:ListBucket |
| 2 | put_probe | Загрузка пробного объекта (__probe__/health-*.txt) |
| 3 | get_probe | Чтение того же объекта |
| 4 | delete_probe | Удаление пробного объекта |
| 5 | presign_get | Генерация подписанного GET URL (для отдачи фронтенду) |
| 6 | presign_post | Генерация подписанного POST URL (для прямой загрузки с устройства аппликанта) |
| 7 | list_objects | Листинг объектов по префиксу |
| 8 | category_dispatch | Корректное разделение по категориям (documents / selfies / ...) |
| 9 | health_quick | Базовый health-check провайдера |
| 10 | probe_cleanup | Удаление всех созданных пробных объектов |
| 11 | public_access | Анонимное чтение должно быть запрещено (Block Public Access) |
| 12 | clock_skew | Расхождение часов сервера и клиента не превышает 60 сек |
| 13 | multipart_upload | Корректная работа multipart-загрузки больших файлов |
| 14 | copy_object | Поддержка серверного копирования (требуется для миграции shared → tenant) |
Дополнительный 15-й probe (sse_enforcement) запускается при sse_mode = "sse_kms" и подсказывает bucket policy для запрета загрузок без шифрования.
Ответ содержит per-probe детализацию:
{
"data": {
"report": {
"results": [
{ "name": "head_bucket", "passed": true, "duration_ns": 145000000 },
{ "name": "put_probe", "passed": false,
"error": "AccessDenied",
"action_hint": "Grant s3:PutObject on bucket prefix" }
],
"all_passed": false
},
"all_passed": false
}
}Для каждой проваленной проверки возвращается action_hint — конкретная подсказка («добавьте s3:PutObject», «снимите Public Access»). Validation возвращает 200 OK и при провале части проб (с all_passed=false) — это позволяет UI показать чек-лист, не парся error envelope.
Активация
После успешной валидации конфигурация переводится в активное состояние:
POST /v1/admin/storage/configs/{id}/activate
X-Confirm-Tenant: <tenant UUID>Заголовок X-Confirm-Tenant обязателен — он защищает от случайных активаций. Значение должно совпадать с UUID текущего тенанта.
После активации:
- Все новые загрузки (документы, селфи и т. д.) идут сразу в подключённый бакет тенанта.
- Исторические данные в общем хранилище копируются в фоне.
Действие фиксируется в журнале аудита (tenant.updated + action=storage_config_activated).
Миграция исторических данных
Активация сразу переключает новые загрузки в подключённый бакет. Файлы, загруженные ранее, остаются доступными, пока OneKYC копирует их в бакет тенанта.
Параметры
- Rate-limit: 10 МБ/с на тенанта по умолчанию.
- Проверка целостности: скопированные объекты проверяются после загрузки.
- Grace period: 30 дней между завершением миграции и удалением копий из общего хранилища.
Состояния задачи миграции
| Статус | Описание |
|---|---|
pending | Задача создана, ожидает запуска |
running | Идёт копирование |
paused | Приостановлено (только через супер-админа) |
completed | Все объекты успешно скопированы |
failed | Достигнут лимит retry; см. last_error |
cancelled | Тенант инициировал Disable во время миграции |
Запрос статуса миграции
GET /v1/admin/storage/migration/currentОтвет содержит счётчики total_objects / copied_objects / failed_objects, прогресс в байтах, started_at, completed_at, последнюю ошибку.
Мониторинг здоровья
GET /v1/admin/storage/healthВозвращает последний снимок здоровья хранилища:
| Поле | Значение |
|---|---|
status | healthy / degraded / unhealthy |
last_check_at | Время последней проверки |
last_success_at | Время последней успешной проверки |
consecutive_failures | Количество подряд неуспешных проверок |
last_error / last_error_code | Последняя ошибка |
alert_sent_at | Когда отправлено уведомление о деградации |
Health-check выполняется автоматически в фоне (легковесный probe). При нескольких подряд неуспешных проверках администратор тенанта получает email-уведомление.
Ротация credentials
Если ключи скомпрометированы или подходит срок плановой ротации:
POST /v1/admin/storage/configs/{id}/rotate
Content-Type: application/json
{
"access_key_id": "<новый AccessKey>",
"secret_key": "<новый SecretKey>"
}Для AWS S3 обычно ротируются aws_role_arn и пересоздаётся ExternalID; для GCS — новый service_account_json; для Azure — новый Account Key / SAS Token.
После ротации статус сбрасывается на pending — необходимо снова запустить Validate, и только после успешной валидации конфигурация снова становится активной (Activate).
Отключение
Если тенант хочет вернуться к общему хранилищу:
POST /v1/admin/storage/disable
X-Confirm-Tenant: <tenant UUID>
Content-Type: application/json
{
"reason": "Описание причины (минимум 10 символов)"
}Эффект:
- Конфигурация переводится в статус
disabled. - Все новые загрузки снова идут в общее хранилище OneKYC.
- Незавершённая миграция отменяется (статус
cancelled). - Файлы, уже записанные в бакет тенанта, НЕ удаляются — тенант сохраняет полный контроль над своим бакетом и может удалить их самостоятельно.
- Эти файлы остаются доступными, пока конфигурация не будет удалена.
Поле reason обязательно (минимум 10 символов) — оно фиксируется в журнале аудита для последующего разбора инцидентов.
Чтобы снова подключить собственный бакет, потребуется создать новую конфигурацию с нуля.
Удаление конфигурации
DELETE /v1/admin/storage/configs/{id}Удалить можно только конфигурацию в статусе pending, validated или failed. Активную конфигурацию необходимо предварительно перевести в disabled через эндпоинт /disable.
Шифрование credentials
Все секреты (secret_key, service_account_json, ключи Azure) хранятся в БД в зашифрованном виде:
- Алгоритм — AES-256-GCM (envelope encryption).
- DEK (Data Encryption Key) выводится из master-ключа OneKYC через HKDF-SHA256 с UUID тенанта в качестве salt и version-tagged info-строки.
- Master-ключ хранится в переменной окружения
STORAGE_MASTER_KEY(не пересекается сENCRYPTION_KEY). - При смене формата шифрования старые ciphertexts продолжают расшифровываться через
credentials_version, новые шифруются текущей версией.
Секреты невозможно прочитать из API — даже у супер-админа OneKYC нет интерфейса для их извлечения. В случае утраты — единственный путь восстановления — ротация (/rotate) с новыми credentials.
Часто задаваемые вопросы
Сколько стоит BYOB?
Со стороны OneKYC — бесплатно (опция доступна на любом тарифе). Расходы на хранение и трафик оплачивает тенант напрямую своему облачному провайдеру (AWS, GCP, Cloudflare и т. д.).
Увеличится ли latency загрузок?
Незначительно. OneKYC использует presigned URL — браузер аппликанта загружает файл напрямую в бакет тенанта, минуя API OneKYC. Latency определяется выбранным регионом бакета (рекомендуется выбрать регион, ближайший к большинству пользователей).
Что произойдёт при недоступности AWS / KMS?
Загрузки на тот период вернутся с ошибкой 5xx; OneKYC не «откатывается» автоматически на shared (это нарушило бы compliance-инвариант). При длительной недоступности администратор тенанта может выполнить Disable — и записи снова пойдут в общее хранилище.
Health-check фиксирует деградацию и отправляет email-уведомление при нескольких подряд неуспешных проверках.
Можно ли иметь две активные конфигурации одновременно?
Нет. На тенанта одновременно может быть активна только одна конфигурация. Чтобы переключиться на другой бакет, текущую активную конфигурацию нужно сначала Disable, затем создать новую.
Диагностика
| Проверка | Типичная причина провала | Действие |
|---|---|---|
head_bucket | Бакет не существует / нет s3:ListBucket | Проверьте имя бакета, добавьте права |
put_probe | Нет s3:PutObject | Проверьте permission policy |
get_probe / delete_probe | Нет s3:GetObject / s3:DeleteObject | Добавьте права |
presign_get | Endpoint неверно сконфигурирован | Проверьте endpoint для S3-compat |
public_access | Бакет отвечает на анонимные запросы | Включите Block Public Access (AWS) / publicAccessPrevention=enforced (GCS) / allowBlobPublicAccess=false (Azure) |
clock_skew | Расхождение часов превышает 60 сек | Настройте NTP на стороне сервера-источника |
multipart_upload | Провайдер не поддерживает multipart или нет прав на сегменты | Проверьте поддержку multipart у провайдера |
copy_object | Нет s3:CopyObject или копирование между бакетами заблокировано | Добавьте права s3:GetObject + s3:PutObject на исходный и целевой бакет |
Все ошибки также фиксируются в журнале аудита (storage_config_validated / storage_config_activated / storage_config_disabled).
Устранение неполадок
Прогресс показывает 0 / 0 N с ошибками
Если на свежей активации всё ещё 0 / 0 — значит в общем хранилище нет файлов для миграции, новые загрузки появятся по мере работы. Это ожидаемое состояние для нового тенанта.
Статус Завершено, но строка N с ошибками
Если часть файлов не удалось скопировать, задача переходит в failed. OneKYC автоматически повторяет попытку, а на панели появляется кнопка Повторить миграцию для немедленного ручного перезапуска.
Если задача остаётся failed после нескольких попыток — посмотрите текст last_error на панели. Самые частые причины:
signature does not match— в credentials есть пробелы / переводы строк, либоregionне совпадает с сегментом URL endpoint. Сделайте rotate, вставьте ключи БЕЗ хвостовых переводов строки.permission denied— bucket policy запрещаетs3:PutObjectили у IAM-роли нет inline-полиса. Проверьте политику через мастер.bucket not found— бакет был удалён или переименован вне OneKYC.
Расшифровка кодов здоровья
Health-панель показывает короткий код ошибки и локализованную подсказку. Полная таблица:
| Код | Значение | Действие |
|---|---|---|
CONFIG_NOT_FOUND | Строка конфига отсутствует в БД | Запустите Activate заново |
CREDENTIALS_INVALID | Драйвер отклонил credentials | Rotate со свежими ключами |
EXTERNAL_ID_MISMATCH | AWS AssumeRole external ID не совпадает | Вставьте External ID в trust policy IAM-роли |
TENANT_STORAGE_UNAVAILABLE | Провайдер временно недоступен | Подождите следующей проверки; если повторяется — обратитесь к провайдеру |
PERMISSION_DENIED | Bucket policy блокирует операцию | Скорректируйте bucket / IAM policy |
BUCKET_NOT_FOUND | Бакет удалён вне OneKYC | Создайте бакет заново ИЛИ Disable + новый конфиг |
SIGNATURE_MISMATCH | Расхождение региона или пробелы в ключах | Сделайте rotate, исправьте поле региона |
HEALTH_CHECK_FAILED | Общий случай | Смотрите last_error для деталей |
Path-style адресация
S3-совместимые провайдеры используют разные способы адресации. Одни бакеты работают через path-style URL (host/bucket/key), другие через virtual-hosted URL (bucket.host/key).
OneKYC сохраняет выбранное значение и использует его как есть при создании storage provider. Следующий шаг Validate проверяет бакет именно в этом режиме; если провайдер отклоняет способ адресации, вернитесь назад, переключите Use path-style и запустите validation снова.
См. также: Журнал аудита, Настройки тенанта.