API-ключ устроен максимально неудобно с точки зрения безопасности: это строка без пароля, без второго фактора и без привязки к устройству. У кого строка — тот и тратит ваши деньги, причём с любого адреса и в любое время. Никакого «подтвердите вход» не будет.
Отсюда два практических вопроса, на которые эта статья отвечает по порядку: где ключ должен лежать, чтобы его было трудно забрать, и как заметить, что его всё-таки забрали. Третий вопрос — что делать после — вынесен в отдельную процедуру в конце.
#Как ключи утекают на самом деле
Не через взлом провайдера. Обычные каналы скучнее и оттого распространённее:
- Репозиторий. Ключ попал в файл конфигурации, файл попал в коммит. Публичные репозитории сканируются автоматически — счёт идёт на минуты, а не на дни. История коммитов при этом хранит ключ и после того, как вы его «удалили» из текущей версии файла.
- Скриншот и переписка. Ключ в кадре при демонстрации экрана, в чате поддержки, в тикете.
- История команд оболочки. Ключ, переданный аргументом команды, остаётся в истории и в списке процессов.
- Чужое приложение. Вы вставили ключ в клиент, о котором знаете только название. С этого момента ключ у его автора.
- Резервная копия базы. Если приложение хранит ключ в открытом виде, ключ есть в каждом бэкапе, включая тот, что лежит в облаке.
- Логи. Ключ, попавший в отладочный вывод, живёт в логах ровно столько, сколько живут логи.
#Уровни защиты: от худшего к лучшему
Смотрите на последнюю колонку: она отвечает на вопрос, от чего конкретно защищает уровень. «Безопасно вообще» не бывает.
| Уровень | Где лежит ключ | От чего защищает |
|---|---|---|
| 0 | в коде или в конфиге репозитория | ни от чего |
| 1 | в файле .env вне репозитория |
от случайного коммита |
| 2 | в переменной окружения процесса | от чтения файла проекта |
| 3 | в менеджере паролей, вставляется руками | от кражи с диска сервера |
| 4 | в зашифрованном хранилище приложения | от чтения базы без пароля |
| 5 | в системном хранилище секретов | от доступа других процессов |
Уровень 1 — минимум, ниже которого опускаться нельзя ни при каких обстоятельствах. Как настроить его так, чтобы он работал не на честном слове, разобрано в материале про секреты в .env и git-хуки.
Важная оговорка ко всей таблице: шифрование хранилища защищает от чтения диска, а не от работающего приложения. Пока приложение отправляет запросы, ключ у него в памяти в открытом виде. Любой уровень выше нулевого — это защита от кражи носителя, копии базы или файла, а не от компрометации самого сервиса.
#Как хранит ключи Persona — без прикрас
Здесь нужно сказать неудобное прямо, до того как вы найдёте это сами.
Ключи участников хранятся в базе в открытом виде. Когда участник подключает своего провайдера, строка вида byo_api_key_<провайдер> пишется в таблицу пользовательских настроек без шифрования. Это уровень 1 из таблицы выше — с оговоркой, что защищает он только от коммита, но не от того, у кого есть доступ к файлу базы или к её резервной копии.
Ключ владельца инстанса может лечь в зашифрованное хранилище — если установлен криптографический пакет и владелец задал мастер-пароль. Но при недоступности хранилища ключ уходит в обычную таблицу настроек, то есть тоже в открытый вид. Механика самого хранилища и мастер-пароля описана в материале про пароль и хранилище для заметок.
Что из этого следует для вас, по пунктам:
- Выпускайте для Persona отдельный ключ, а не тот, которым пользуетесь в других приложениях.
- Ставьте на этот ключ лимит трат в кабинете провайдера, если провайдер это позволяет.
- Если разворачиваете Persona у себя — относитесь к файлу базы как к файлу с секретами: ограничивайте права доступа и шифруйте резервные копии.
- Если пользуетесь размещённой версией — ключ лежит на её сервере, и это нужно учитывать при выборе, какой ключ туда вставлять. На размещённой версии, кроме того, стоит Яндекс.Метрика с вебвизором, который пишет клики и навигацию по страницам; при развёртывании у себя счётчика нет.
Мы не можем предложить вам аргумент «проверьте код»: репозиторий закрыт, публичной лицензии нет. Это осознанное ограничение позиции, и оно вместе с другими слабыми местами разобрано в материале о том, почему мы проигрываем по некоторым пунктам.
#Признаки утечки
Ключ не сообщает о том, что им пользуется кто-то ещё. Замечают это по косвенным признакам:
- Расход вырос без изменения вашей активности. Самый надёжный признак. Смотрите дневной график в кабинете провайдера, а не месячную сумму.
- Запросы к моделям, которые вы не вызываете. Особенно к дорогим или к тем, что не поддерживаются вашим приложением.
- Активность в часы, когда вы не работаете. Ровный поток ночью — почти всегда не вы.
- Письмо от провайдера об обнаружении ключа в публичном источнике. Часть сервисов сканирует публичные репозитории и отзывает ключ сама.
- Ошибка 401 без ваших действий. Иногда это уже отзыв ключа провайдером после обнаружения утечки.
#Процедура отзыва и замены
Порядок важен: сначала перекрыть, потом разбираться.
- Отзовите ключ в кабинете провайдера. Не «замените в приложении», а именно отзовите — иначе старый продолжает работать.
- Выпустите новый ключ с ограничением по лимиту трат.
- Замените его во всех местах, где использовался старый. Составьте список заранее: приложения, серверы, скрипты, переменные окружения на машинах разработки.
- Проверьте историю расхода за период с момента вероятной утечки. Это нужно и для оценки ущерба, и для обращения к провайдеру.
- Найдите канал утечки. Если ключ был в репозитории, помните: удаление из файла не удаляет его из истории коммитов. Порядок действий при утечке в репозиторий разобран в материале про ротацию утёкших учётных данных.
- Проверьте соседние секреты. Утечка редко бывает одиночной: если ключ уехал в коммит, вместе с ним обычно уехало что-то ещё.
#Проверка результата
- Старый ключ в кабинете провайдера значится отозванным.
- Запрос со старым ключом возвращает 401.
- Приложения работают на новом ключе, и в разделе использования виден расход именно по нему.
- Через сутки график расхода вернулся к вашей норме.
Четвёртый пункт — единственный, который подтверждает, что утечка закрыта. Если расход продолжается, значит утёк не только этот ключ.
#Что может пойти не так
- Ключ отозван, а приложение продолжает работать. Значит оно использует другой сохранённый ключ. Проверьте все настроенные провайдеры, а не только текущий.
- Новый ключ не работает. Чаще всего — невидимые символы при копировании или ограничение по правам.
- Расход не упал после ротации. Утёк ещё один ключ или скомпрометирован сам сервер.
- Ключ был у посредника. При работе через шлюз у вас две точки хранения: ваша и его. Что это значит и как проверять посредника — в статье про агрегаторы с оплатой российской картой.
#Частые вопросы
#Можно ли считать хранение ключа в базе приложения приемлемым?
Это приемлемо ровно настолько, насколько вы контролируете доступ к базе и её резервным копиям, и настолько, насколько ограничен сам ключ. Отдельный ключ с лимитом трат в базе — управляемый риск. Общий ключ без ограничений в той же базе — нет.
#Насколько защищает мастер-пароль приложения?
Он защищает содержимое хранилища от чтения без пароля: копия базы без пароля бесполезна. Он не защищает от компрометации работающего приложения, потому что во время работы ключ находится в памяти процесса в открытом виде. Это разные угрозы, и путать их не стоит.
#Что делать, если ключ попал в публичный репозиторий?
Считайте его скомпрометированным немедленно, независимо от того, сколько он там пробыл: публичные репозитории сканируются автоматически. Отзовите ключ первым действием, до всякой чистки истории. Чистка истории без отзыва бесполезна.
#Как понять, какое приложение сливает ключ?
Только по отдельным ключам на каждое приложение — иначе источник неотличим. Если ключ был общим, единственный путь — отозвать его, выпустить новые по одному на приложение и наблюдать, где повторится аномалия.
#Стоит ли шифровать резервные копии базы?
Да, если в базе лежат ключи в открытом виде — а в большинстве приложений это именно так. Резервная копия обычно защищена хуже боевой базы: она копируется, переносится, лежит в облаке и живёт годами.
#Есть ли вариант, при котором ключ не нужен вовсе?
Есть: собственная модель на собственном железе. Ключа нет, значит и утечь нечему. Взамен появляются требования к памяти машины и более слабые ответы на сложных задачах по сравнению с топовой облачной моделью. Обзор всех вариантов доступа — в материале о том, где взять ключ к языковой модели.