
Привет! Меня зовут Игорь Михалюк, я Tech Lead команды IAM в MWS. Сегодня поговорим, как мы делаем Identity and Access Management, или IAM, в облаке MWS.
Расскажу, как мы решили разграничивать доступ в облаке, а ещё о сложных случаях ограничения радиуса атаки на ресурсы наших клиентов. За время разработки мы столкнулись со множеством трудных решений, компромиссов, о которых тоже поделюсь в этой статье.
Аутентификация пользователей
Итак, мы решили построить облако, то есть некоторую систему, в которой пользователи могут брать в аренду вычислительные ресурсы, выстраивать свои ИТ-решения, хранить данные своих клиентов. При этом объём ресурсов, которые может получить пользователь, может быть меньше, чем реальный объём CPU, памяти, дисков на наших серверах. Это значит, что на одном и том же железном сервере могут одновременно работать несколько пользователей облака и просто необходимо, чтобы они не знали об этом и не могли получить доступ к секретной информации друг друга.
Что в этом случае надо решить в первую очередь? Конечно, найти способ аутентификации пользователей.
Для аутентификации уже давно придумано множество всяких методов, начиная от простого «логин + пароль», заканчивая интеграцией с системами типа «Госуслуги», которые могут юридически подтвердить личность пользователя. В случае аутентификации пользователей облака, простой проверки имени пользователя и пароля недостаточно. Облако — провайдер хостинга, поэтому личность каждого клиента должна быть подтверждена в соответствии с законодательством. Чтобы это сделать, есть множество способов. Например, списать и вернуть 10 рублей с карты клиента.
Ещё один способ — использование внешнего провайдера аутентификации через протоколы OIDC и ему подобные. В облаке мы поддерживаем основные протоколы SSO, которые могут использоваться, чтобы интегрировать провайдера авторизации к нам.
Например, в МТС есть отличное решение, которое соответствует всем требованиям законодательства и легко интегрируется с любой системой, — это МТС ID. Универсальный поставщик аутентификации для граждан РФ по номеру телефона. С него мы и решили начать.
После интеграции с МТС ID у нас появились пользователи, то есть субъекты, с именем и фамилией. Аутентификация пользователей означает, что наша система выдала пользователю так называемый IAM-токен, предъявив который в любом API облака пользователь может доказать, что он именно тот, за кого себя выдаёт. И всё бы было хорошо, но, помимо добропорядочных пользователей, к нам в облако могут заходить и злоумышленники, и наша задача теперь — защитить данные, которые нам доверили пользователи.

Ресурсная модель
Что же делать, если у нас уже есть аутентифицированный пользователь и он хочет создать, например, виртуальную машину с диском и начать хранить там свои данные. Самый простой способ — прописать, что конкретный ресурс в облаке «принадлежит» конкретному пользователю и только он может управлять им и данными, которые он содержит.
С другой стороны, наше облако — инструмент универсальный, который подходит как индивидуальным клиентам, так и корпоративным. И в случае крупного клиента, на которого работают сотни, а то и тысячи сотрудников и каждый исполняет свою роль, — такой подход не подойдёт. Нужно обеспечить совместный доступ нескольких пользователей к общим ресурсам. А ещё учесть роли, которые они исполняют в компании.
Мы проанализировали большое количество наших потенциальных клиентов и в результате долгих размышлений пришли к такой структуре, которая подходит всем, начиная от крупных корпораций и заканчивая индивидуальными клиентами.
Корень нашей ресурсной модели — Организация. Это абстрактная модель любого клиента, который хочет пользоваться нашими ресурсами. В рамках Организации пользователи могут создавать проекты. Проект — это базовый контейнер, в рамках которого выделяются любые ресурсы пользователей: диски, виртуальные машины, сети, адреса — всё это относится к тому или иному проекту. Для того чтобы можно было более точно отразить организационную структуру клиента, проекты в рамках организации могут быть объединены в иерархию папок. Например, разные бизнес-юниты в организации могут иметь свои папки в облаке. Совсем как файлы на диске.
Как же теперь нам сформулировать определение роли, которую будет исполнять пользователь? Тут нам поможет подход, который известен как RBAC, Role Based Access Control. Мы его реализуем так:
Все элементарные действия, которые может выполнить пользователь в облаке, называются «разрешения», или permissions. Когда, например, пользователь пытается создать диск — у него проверяется наличие специального разрешения, соответствующего этому действию. Таких действий в облаке — тысячи, и, естественно, оперировать на уровне элементарных разрешений — то ещё удовольствие. Поэтому мы объединяем разрешения в роли. Получается, что роль — это набор разрешений делать те или иные действия в облаке.
Отлично, у нас есть роли, есть дерево ресурсов в организации клиента. Как теперь это всё связать? Процесс назначения прав в облаке у нас называется привязкой роли. Это действие, которое на вход принимает три параметра:
Субъект — пользователь, которому выдаётся роль.
Роль как таковая.
Узел нашей ресурсной модели, который будет ограничивать область действия этой роли для конкретного пользователя.
Давайте рассмотрим на примере. В облаке есть роль — администратор виртуальных машин, которая позволяет сделать что угодно с любой виртуальной машиной. Пусть теперь у нашего клиента есть три среды: dev, stage и prod. И мы выдаём роль администратора ВМ-разработчику, но при этом ограничиваем область действия такого разрешения. Для этого мы привяжем роль к папке под названием dev. Таким образом наш разработчик сможет совершать любые действия над виртуальными машинами в папке dev, но при этом до ВМ в папке stage у него доступа не будет.

Сервисные аккаунты
Итак, у нас уже появилось довольно симпатичное облако, куда могут заходить даже корпоративные пользователи со своей сложной организационной структурой. Но давайте на секунду представим, сколько может быть задействовано виртуальных машин в типичном проекте? Конечно, это зависит от проекта, но это точно десятки, если не сотни ВМ, дисков, IP-адресов и всего прочего.
Управлять вручную таким количеством ресурсов невозможно. Поэтому нам нужно дать пользователю помощников — учётные записи для автоматизации тех или иных процессов. Это такие виртуальные роботы, которые исполняют скрипты, что-то проверяют, запускают и останавливают виртуальные машины, делают бэкапы и многое другое. Таких роботов пользователь создаёт сам и наделяет их правами, естественно, только теми, которые он может выдать и использует для автоматизации процессов.
Но роботы — не люди, у них нет мобильного телефона, как им пройти через двухфакторную авторизацию MTS ID или другого OIDC-провайдера? В общем случае есть два сценария, где нужна аутентификация сервисного аккаунта, и для каждого из них у нас есть решение.
Исполнение скрипта в доверенной среде
Первый сценарий — это аутентификация сервисного аккаунта для исполнения скрипта, который находится в доверенной среде — внутри облака. Например, исполнение скрипта происходит на ВМ. В этом случае пользователь привязывает сервисный аккаунт к ВМ, сообщая облаку, что любой код, который работает внутри этой ВМ, при желании и необходимости может выполнять действия от имени этого сервисного аккаунта. Чтобы зайти на машину, нужно выполнить авторизацию.
Тут мы имеем цепочку авторизаций без разрывов, что позволяет, используя механизм metadata server, выдать скриптам или приложениям, которые работают на ВМ, IAM Token сервисного аккаунта, который привязан к ВМ. Время жизни этого токена, его валидность и прочие параметры, которые влияют на безопасность, при этом определяет само облако. Таким образом, риск утечки токена минимизируется.
Исполнение скрипта вне облака
Но что, если скрипт, который надо выполнить, находится вне облака? Например, на ноутбуке клиента. Тут не обойтись без выпуска статических ключей доступа, срок действия которых определяет сам пользователь.
Пользователь генерирует на своём компьютере криптографическую пару несимметричных ключей — открытый и закрытый. Открытый ключ передаётся в облако и привязывается к конкретному сервисному аккаунту. Затем, используя закрытый ключ, скрипт, который хочет получить временный IAM Token, подписывает специально сформированный JWS-токен, который обменивает на IAM Token путём вызова специального API.
По желанию облако может сформировать ключевую пару на своей стороне и однократно передать пользователю закрытый ключ, который не сохраняется на стороне облака. В использовании никакой разницы нет — они работают одинаково. После того как сервисный аккаунт получил свой IAM Token, он сможет выполнять любые действия в облаке, на которые ему хватит прав.

AWS совместимая авторизация
Однако на этом история с аутентификацией сервисных аккаунтов не заканчивается. Дело в том, что любое зрелое облако сегодня невозможно представить без сервиса «Объектное хранилище» (Object Storage). Как и в большинстве зрелых облаков, этот сервис есть и у нас.
Первыми такой сервис сделали ребята из AWS. У них он называется S3. И как любой сервис в AWS, он поддерживает только авторизацию, разработанную в AWS, основанную на подписании запросов алгоритмом HMAC+SHA256. Мы же для авторизации используем Bearer-токены, то есть строчки, которые просто передаются в заголовке Authorization. Такой механизм может реализовать практически любой разработчик с минимальным набором инструментов. Условно, если у него есть bash и curl, этого уже достаточно. Аутентификация выполняется командой:
curl -H "authorization: bearer <>"
Алгоритм подписания запросов в самом AWS намного более сложный. Он требует более глубокого понимания работы алгоритмов шифрования и структуры http-запроса. Конечно, сейчас он уже реализован во многих системных утилитах и в том же curl последней версии уже можно подписывать запросы этим алгоритмом, но за то время, пока эту поддержку делали все, пользовались для авторизации сторонними утилитами и sdk, которых появилось огромное множество. Зачастую эти утилиты поддерживают только авторизацию на основе HMAC-ключей.
AWS был первый, и всем, кто делал аналогичный сервис, пришлось делать его совместимым с протоколом S3, потому что это уже стало популярным решением и зачастую его поддержка нужна нашим потенциальным пользователям. Наш сервис S3 на 100% совместим с этим протоколом и может работать с любыми утилитами и sdk, которые разработаны для S3. Из-за этого у нас и возникла необходимость поддержки AWS совместимой авторизации в сервисе Object Storage.

Для авторизации в сервисе Object Storage пользователь через консоль создаёт себе пару секретов, AccessKey и SecretId. Мы называем их HMAC-ключами. Используя их, можно получить доступ до бакета, который создан в проекте.

Ещё у AWS есть способ управления доступом на уровне ресурсов. У них это называется Policy. Если такая Policy установлена на бакете, то она называется Bucket Policy. Такие политики доступа могут ограничивать и разрешать доступ к контенту внутри бакета, основываясь не только на правилах RBAC, но и дополнительно проверять контекст запроса. Например, получать IP-адрес, использовать имя пользователя как часть пути файла, куда разрешён или запрещён доступ, и даже анализировать время запроса.
Естественно, такие политики необходимо было реализовать и нам. И тут нам не хватило модели RBAC, потому что статическое назначение ролей на ресурсы никак не помогает решить проблему динамического разграничения доступа на основе контекста запроса.
И мы рассмотрели расширение модели контроля доступа до ABAC. В этой модели решение о доступе принимается не только на уровне статического распределения ролей, но и на основе анализа контекста, который выполняется на специальном DSL для вычисления логических выражений.
Пока что в нашем облаке — это внутренняя функциональность и она недоступна для редактирования пользователями. Но мы активно используем эти возможности, в частности для реализации Bucket Policy.
Однако реализация HMAC-ключей, несмотря на свою сложность, несёт в себе возможность реализовать такой полезный механизм, как «преподписанные» URL.
Представьте, что вашей системе нужно загружать файлы, причём файлы разного размера, формата и т. д. В классическом подходе вам нужен сервис, который примет от пользователя файл, положит его в хранилище, сделает обработку. Вроде бы просто, но загрузка файлов — это одна из самых непростых операций, особенно если файлы большие. Нужно реализовать загрузку по частям, возобновление загрузки, если сеть пропала у пользователя, и много других аспектов.
В протоколе S3 и, соответственно, в нашем сервисе Object Storage для таких целей реализован механизм получения публичного URL, время действия которого ограничено. Во время, пока URL действует, пользователь может загрузить файл по этому URL напрямую в Object Storage, и ему не нужно будет настраивать ресурсы в облаке для организации процесса загрузки файлов. Как только файл будет загружен, Object Storage оповестит об этом пользовательскую ВМ посредством создания сообщения в очереди или другими доступными средствами.
В этом случае процедура загрузки файла выглядит так:
Пользователь хочет загрузить файл и сначала его клиент идёт в сервис и запрашивает URL.
Сервис получает «преподписанный» URL у Object Storage, указав путь, куда сохранить файл, и отдаёт его клиенту пользователя.
Клиент пользователя загружает файл по этому URL.
Наш сервис получает оповещение, что в бакете появился файл с нужным именем и можно начать его обработку.

Сервисные агенты
Итак, у нас получилось довольно функциональное облако с точки зрения IAM. Мы умеем разграничивать доступ между пользователями, создавать и авторизовывать сервисные аккаунты, которые позволяют автоматизировать управление ресурсами в облаке. Но только ли эти субъекты могут получать доступ до ресурсов пользователя в облаке? И тут, когда мы анализировали нашу модель доступа, мы поняли. Нет. Есть ещё один тип субъектов, а именно, само облако.
Давайте расскажу на примере. Допустим, вы решили собрать компьютер для дома. Какие у вас есть варианты?
Купить готовую конфигурацию.
Заказать сбоку в магазине, задав параметры.
Купить запчасти и собрать компьютер самостоятельно.
Так же обстоит дело с виртуальными машинами у нас в облаке. За исключением того, что готовых у нас нет — и мы все их «собираем». Но суть остаётся та же. Пользователь может разместить у нас заказ на создание ВМ c нужными ему параметрами, например 2 CPU, 10GB RAM, диск на 10GB и публичный IP-адрес, а «сборщик» — у нас он называется Compute, сам создаст необходимые ресурсы, выделит CPU и RAM и оповестит пользователя, когда конфигурация будет готова.
Понятно, что на всё это нужно время, и иногда значительное, поэтому сервис Compute собирает ВМ в фоне, действуя от своего имени. Так же и в реальном мире: оформив заказ, вы не видите, как сотрудник идёт на склад, получает комплектующие и формирует ваш заказ. У сотрудника магазина есть права, которых у вас, как у покупателя, — нет.
Второй вариант, когда вы заранее покупаете комплектующие и сами собираете свой компьютер, у нас тоже поддерживается. Вы можете заранее создать диск, выделить IP-адрес и прочее, а потом из этих «запчастей» собрать себе виртуальную машину.
Более того, продолжим аналогию. Вы ведь можете распределить обязанности и, например, попросить друга, который понимает в дисках, — купить хороший диск, а кто понимает в видеокартах — хорошую карту. В облаке так же. Вы, как администратор проекта, можете создать роль, которая только создаёт диски, и отдать её отделу, который занимается Storage, а сетевые инженеры у вас могут заниматься исключительно IP-адресами. При этом разработчики могут только из этих частей, созданных коллегами, — собирать ВМ и всё. То есть гибкость устройств производственного процесса тут большая.
Проверка прав у нас выглядит так: когда пользователь размещает заказ на создание виртуальной машины, мы проверяем, а имеет ли он права на все запрошенные в заказе ресурсы. Если брать наш пример с заказом, проверяем, оплатил ли покупатель все детали.
Если доступ есть — мы передаём заказ на выполнение. И тут у нас опять возник вопрос. Кажется, что сервис Compute, который будет собирать виртуалки, — это же сервис облака. Он создаёт виртуалки для всех проектов, поэтому что мешает дать ему права — делать что угодно с любым ресурсом облака, такой «режим бога». Да, такие широкие полномочия внутренних сервисов облака действительно решат проблему, но создадут дополнительную.
Допустим, у вас есть проект «Добрый», в котором пользователь создал ВМ с диском. И разместил там данные своих пользователей. И тут появляется злоумышленник, который хочет эти данные украсть. Он обманом убеждает нашего пользователя дать ему право подсоединить диск пользователя к виртуальной машине, которую создал злоумышленник. И наш мошенник размещает заказ на создание такой виртуальной машины. Проверка прав в этом случае не обнаружит ничего подозрительного и позволит злоумышленнику такую заявку разместить. Но тут добрый пользователь осознаёт свой промах и отбирает права у мошенника. Но заявка уже размещена, а так как в этой модели Compute имеет права суперпользователя — он исполнит заявку злоумышленника и произойдёт непоправимое. Нас это категорически не устраивает. Мы создаём в облаке многослойную систему защиты данных пользователей, которая исключает подобные сценарии. И для этого мы разработали концепцию сервисных агентов.
Сервисный агент — это тоже служебная учётная запись — токен, которой может получить только сервис облака. Такая учётная запись создаётся в каждом проекте, и по умолчанию она имеет права только на ресурсы проекта.
Если возвратиться к нашему примеру, то, когда злоумышленник разместит заказ, сервис Compute, действуя от имени сервисного агента в проекте «Зло», получит отказ на присоединение диска из проекта «Добрый». Потому что это разные проекты и у сервисных агентов Compute в них нет кросс-проектных прав.

Межпроектный доступ
Но иногда всё же межпроектный доступ бывает очень нужен и полезен. Например, при миграции данных между проектами. Вот, допустим, мы сделали какой-то MVP-проект, который онлайн продаёт фигурки к Новому году. Большую часть мы сделали в ручном режиме. Изготовление, доставка, складской учёт. А продажа идёт через наш MVP. Проект оказался удачным, мы накопили базу клиентов и теперь хотим сделать уже промышленное решение. Оно разрабатывается с нуля и содержит в себе уже малые крохи от MVP, но перед запуском базу клиентов из MVP надо перенести в промышленное решение. И тут мы, как владельцы проекта MVP, можем дать право сервисному агенту Сompute в целевом проекте присоединять диск из нашего проекта и читать оттуда данные. Ну и на этом всё. Данные смигрированы — можно потушить MVP и запускать целевое решение.

Таким образом, мы по умолчанию ограничиваем область видимости сервисных агентов рамками проектов, но не запрещаем межроектный доступ, но в этом случае это должно быть взвешенное решение пользователя.
Заключение
Ну вот мы и подошли к концу нашего рассказа о том, как мы делали IAM в облаке MWS. Теперь вы знаете, как устроен IAM в облаке и зачем он нужен: создавать и ограничивать доступы в облаке, давать возможность делать автоматизированные скрипты, которые вместо пользователя будут выполнять ту или иную работу, как можно пользоваться хранилищем файлов. А главное — как контролировать доступ до своих ресурсов не только пользователям, но и самому облаку в случае необходимости. Чтобы изучить вопрос IAM в MWS подробнее, можно почитать статью моего коллеги Андрея Халиуллина, который рассказал о ресурсной модели облака.
Конечно, область IAM не ограничивается только темами, о которых мы с вами поговорили сегодня, есть ещё и политики доступа на уровне организации, доступ федеративных пользователей и прочее, но об этом мы поговорим в будущем.
Зачем мы строим собственное публичное облако? Рассказывает CTO MWS Данила Дюгуров.
Реалити-проект для инженеров про разработку облака.
Рассказываем про архитектуру сервисов платформы ещё до релиза.
Подкаст "Расскажите про MWS". Рассказываем про команду новой облачной платформы MWS.