Жулик, не воруй

Жулик, не воруй


Впервые модель в контуре заказчика я развернул году в 2016. С тех самых пор мне приходится отвечать на вопросы бизнеса - как нам защититься от того, что кто-то захочет украсть наши сеточки?

Я больше склонен к тому, что сам код и веса без команды, которая за ними стоит, обычно (не всегда) имеют невеликую ценность. Всё это хозяйство надо поддерживать, обновлять, дообучать. Но определённые риски для бизнеса, конечно же, есть, поэтому в целом защититься будет не лишним. Какие есть способы? На самом деле их не так много, а стопроцентную защиту вообще обеспечить фактически невозможно. Давайте разбираться.

Вырезаем лишнее

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

Плюсы - не даёт злоумышленникам полный доступ к вашему обучающему коду и данным.

Минусы - у них всё ещё будет доступ к инференс-коду, архитектуре сетки и весам.

Обфускация и компиляция

Второй слой защиты - обуфскация. Простые методы типа python-minifier вряд ли помогут, особенно в эпоху LLM, поэтому сразу перейдём к более тяжёлой артиллерии - PyArmor. Примерно так выглядит код после PyArmor:

from pyarmor_runtime_000000 import __pyarmor__
__pyarmor__(__name__, __file__, b'PY000000\x00\x03\x0c\x00\xcb\r\r\n\x80\x00\x01\x00\x08\x00\x00\x00\x04\x00\x00\x00@\x00\x00\x00\x18$\x00\x00\x12\t\x04\x00N\xb0\xd0eH\x9cSu\xb0\xd1\x84Dae\x80\xef\x00\x00\x00\x00\x00\x00\x00\x00h\x98\xa4\xfe\xed\xa1T\xccg)\xc5s\x13\xb9:s\x1doE\x93\xbd"\xc4\xddG3*H\x03\x9e\x8c\xe5#R\x9br\x85P\xec\xe5\xc4\xf1g\x16\xf9\xba0\xa8\x924=\x10\x02\x1f\xc7\x8c\xddh=\xc9\xbe\xcd #\xf7T#T4\xee\xd3\xef\x9f<\xd2D\x9d\x85\\

и так далее.

Исходный код компилируется в байткод (.pyc-файлы), шифруется и дешифруется при запуске специальным рантаймом, который генерируется при обфускации (pyarmor_runtime.so).

PyArmor предлагает дополнительные плюшки:

  • Установка срока работы скрипта
  • Привязка к конкретному железу (например, жёсткому диску)

Опять же не забываем про нюансы обфускации в Докере - можно использовать multi-stage билды, чтоб оригинальный код не попал в образ.

Плюсы - не особо заинтересованные или скилловые злоумышленники почти наверняка быстро бросят попытки что-то извлечь после обфускации с помощью PyArmor.

Минусы - никак не защищает веса моделей, часть функциональности доступна только в платной версии.

Ещё один популярный инструмент для компиляции Питон-проектов (сам не пробовал) - Nuitka.

Усложнение архитектуры модели

Обфускация кода никак не защищает файл с весами самой модели - torch.load(weights_file), и ваши конкуренты имеют полный доступ к архитектуре и весам модели. Если наша задача - усложнить понимание принципов работы нейронки, можно позаниматься подобными вещами:

  • Переименовать имена слоёв в несоответствующие их назначению
  • Добавить пустые слои (Identity, Conv с нулевыми кернелами)
  • Добавить неиспользуемые ветки модели

Плюсы - будет чуть сложнее разобраться в том, как работает сетка: security through obscurity.

Минусы - очень слабая защита.

Шифрование весов модели

Если хочется защитить сами веса нейронки, то можно их зашифровать как-нибудь так:

Пример кода

Остаётся вопрос, как секьюрно прокинуть ключ в контейнер во время запуска. Простой способ - использовать docker secret create, потом прокинуть его в ямле docker compose, прочитать ключ из /run/secrets/model.key и удалить его.

Плюсы - защищаем наши драгоценные веса нейроночки.

Минусы - в теории, если есть доступ к серверу, веса всё ещё можно украсть из GPU-памяти, но это уже высокий уровень заморочки.

Защита на железном уровне

Если уровень паранойи зашкаливает, надо подключать железную защиту.

Примеры:

  • Intel SGX позволяет запускать код внутри защищённой области памяти (так называемый анклав), которая изолирована даже от root-пользователя. Очевидно, поддерживается только CPU-выполнение. Здесь хорошо описаны принципы работы и основные концепты.
  • NVIDIA Confidential Computing (на H100 и выше) — GPU-память тоже защищается от доступа, шифруется, доступ ограничен только доверенному рантайму.

Есть интересные подходы типа TEESlice - не у всех всё-таки есть H100. Модель до обучения делится на две части - публичная, которая исполняется на GPU, и чувствительная, которая засовывается в защищённый анклав. Делить можно по-разному, самый простой, но и наименее секьюрный способ - последние слои сделать защищёнными.

Плюсы - самая надёжная защита.

Минусы - придётся поразбираться, есть фреймворк BlindAI, но он не поддерживается уже пару лет. Ещё есть Scone, это фреймворк для запуска контейнеров внутри SGX-анклава, но я с ним не работал.

Лицензионный сервер

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

Проверку лицензии удобно реализовать через что-нибудь типа python-licensing, а саму логику лицензирования спрятать через тот же PyArmor. Без валидной лицензии зашифрованные веса модели на загрузятся или система упадёт с ошибкой на инференсе.

Что выбрать?

Несколько простых советов:

  • Писать очень плохой код, чтоб в нём невозможно было разобраться
  • Вырезать всё лишнее из докер-образа, особенно секреты
  • Попробовать прогнать PyArmor на особо чувствительных местах проекта и посмотреть, запустится ли всё на проде
  • Зашифровать веса модели - просто и не очень сложно
  • Техническая защита должна идти только как дополнение к юридической
  • Даже в контуре заказчика доступ к серверу с моделью должен быть у строго ограниченного круга лиц


Report Page