Azure Active Directory. Історія інтеграції на видалення користувача з корпоративних акаунтів

Підписуйтеся на Telegram-канал «DOU #tech», щоб не пропустити нові технічні статті

Привіт, мене звати Катя Грицаєнко, я GDSC КПІ Сo-Founder та ментор, WTM Ambassador, cloud-ентузіаст та Java-консультат у Valtech.

Хочу розповісти про одне з можливих рішень достатньо специфічної проблеми — синхронізації Azure Active Directory з зовнішнім API. Зокрема про те, як змусити Azure AD повідомляти про архівацію користувачів корпоративному інструментові зберігання мультимедіа ресурсів — Bynder.

Навіщо читати далі

У бізнесу завжди були і будуть додаткові інструменти для налагодження внутрішніх процесів, корпоративні акаунти та підписки на голосувалки, сховища медіаресурсів та інші безумовно корисні штуки. І рано чи пізно, питання: «Як впевнитись, що абстрактний Павло був доданий до усіх систем?» або навіть більш цікаве: «Як впевнитися, що той-самий-Павло був видалений відусюди після того, як залишив компанію?» змінить статус із гітотетичного на «А що робити?».

У цій статті мова піде про реальний кейс, на рішення якого було витрачено багато часу саме через специфічність теми. Я сподіваюся, що вона вбереже когось з вас від годин одноманітного ресерчу та пошуків серед документації Microsoft Azure.

Детальніше про задачу

Правильно складене ТЗ — це 90% роботи, тому давайте розберемося щодо наших очікувань від результату.

Допустимо, ми маємо систему, яка зберігає корпоративні медіа ресурси: фото з патюшок, відео презентацій та ін. В ідеалі, кожен працівник компанії має акаунт, що спрощує йому доступ до усього цього добра. В ідеалі також, його ініціалізація має відбуватись автоматично після додавання користувача в Azure Active Directory. І так само автоматично має відбуватися деактивація/видалення з організації.

Коли мова заходить про синхронізацію Azure AD з... чим завгодно, першим ділом на думку спадає інтеграція через SCIM, адже саме таке рішення рекомендує Microsoft у своїй документації.

SCIM

Або System for Cross-Domain Identity Management — це стандарт, створений для синхронізацій даних між системами типу Azure AD та Human Capital Management (HCM) (читай як різні інструменти для онбордингу, рекрутменту та т. п.) По факту, SCIM описує REST endpoint-и: /Users та /Groups; а також сет типових схем для комунікації. Цей підхід зручний, тому що:

  • він безпечний за замовчуванням;
  • добре описаний в офіційній документації, тож ризик «застрягти» з реалізацією достатньо низький;
  • Azure надає його as service, тому отримати бажаний результат можна як діями на порталі так і набором команд для CLI.

Але існує значне АЛЕ: за умовами синхронізації, target-система має також підтримувати SCIM-інтеграцію. Якщо у вас саме такий випадок, то можу порекомендувати приклади інтеграцій, описані у Microsoft:

У реальному кейсі, що ліг в основу цієї статті, tagret-система SCIM інтеграції не підтримувала. Саме тому вирішено прописувати її вручну, починаючи від івентів на рівні AD та закінчуючи клієнтом для зовнішього сервісу.

І тут почалося найцікавіше... Але по порядку.

Альтернатива SCIM-інтеграції

Отже, ваша target-система не підтримує SCIM. Що робити? Для зручності одразу пропустимо стадії «заперечення», «злісті», «торгу» та «депресії», і перейдемо до продуктивного прийняття.

  • Як отримати доступ до івентів Azure AD?
  • Як відловити саме івент деактивації користувача?
  • І нарешті: куди запхати тригер на згадане вище деактивування?

Доступ подій в Azure AD

Логи в Azure AD та якими вони бувають

Гарна новина: Azure Portal надає можливість опрацьовувати івенти за допомогою механізмів логування. Концепт наступний: беремо лог-репорти від Azure AD, групуємо та перенаправляємо їх до Storage акаунту, Event Hub-у, Azure Monitor logs сервісу чи навіть до нашого власного сховища. Головне — знайти рішення, що дозволить зберегти великий обсяг даних з високою складністю.

Azure AD підтримує два типи логування подій:

  • Audit logs надають доступ до інформації про будь-які зміни, що зазнає теннант (такі як управління користувачами, групами, ресурсами);
  • Sign-in logs дозволяють встановити, хто ініціював події, що були відображені у Audit Logs.

Інформація щодо деактивації користувача відноситься до категорії Audit logs, тому будемо використовувати саме їх.

Корисні посилання:

З логами розібрались. Тепер постає наступне питання.

Де зберігати

Варіантів достатньо. Як уже описано вище, Azure дозволяє як використовувати вбудовані рішення (Storage акаунти, Event Hub-и, Azure Monitor logs сервіси), так і власні інструменти. Для нашого кейсу нагальної потреби у third-party services не має. Тому було вирішено використовувати Event Hubs сервіс. Чому? Тому що нам необхідно реалізувати додаткову фільтрацію подій, а Event Hub-и надають зручний механізм вбудованих тригерів для подібних випадків

Рекомендація: за можливості намагайтесь уникнути зайвих інтеграцій. Такий підхід зекономить вам час та нерви, у випадках, коли вам потрібна примітивна фільтрація чи ви не плануєте виконувати проміжні дії з даними високої складності. Принцип «easier, the better» працює тут навіть краще, ніж зазвичай.

Налаштовуємо стрім Audit Logs до Event Hub-у

Про те, як стрімити Audit логи до Event Hub інструментами Azure Portal якісно та деталізовано, написав eax360 у статті Azure Active Directory — Capturing Events in Azure Events Hub. Єдина, проте суттєва, відмінність нашого кейсу полягає в тому, що ми маємо реалізувати додаткову фільтрацію на івент деактивації користувача з Active Directory.

Крок перший: створюємо ресурс групу

Крок другий: створюємо Event Hub, куди стріметимемо дані

У результаті маєте отримати подібну сторінку, усі графіки на нулі, адже ми нічого ще туди не додавали:

Крок третій: імпортуємо Audit Logs до новостворенного Event Hub-y

Для цього потрібно перейти до меню Azure Active Directory > Monitoring > Audit logs та обрати опцію Export Settings.

У Export Data Settings необхідно буде натиснути + Add diagnostic setting та виділити check box AuditLogs, обрати опцію стрімити до Event Hub-у.

Крок четвертий: насолоджуємося

Тепер усі події, що відбуватимуся у AD будуть записані, і ми можемо робити з ними що завгодно. Наприклад, переглядати. Для цього перейдемо назад до сторінки нашого Event Hub-y та оберемо опцію Process data з меню.

Далі ажур надасть вибір між можливими варіантами візуалізації даних. Вони усі, безумовно, корисні, проте для одного лиш перегляду нам вистачить опції Stream Analytics Query.

Важливо: ви не зможете переглядати дані таким чином, якщо ваш хаб використовується іншим процесом порталу. Так, наприклад, коли ми створимо скрипт, що фільтруватиме події та працюватиме з хабом у неперервному режимі, при спробі підключитись через Query Language інтерфейс ви отримаєте помилку:

Фільтрація подій за допомогою Event Hubs

За яким принципом фільтрувати

Audit logs мають формалізовану структуру відповідно до типів операцій, що вони відображають. Типів багато. Дуже багато. Детальніше про них та їхню класифікацію можна прочитати тут. Проте нам необхідно отримати івенти типу зміни статусу користувача на «неактивний».

І тут нас очікує неприємний сюрприз — окремого івенту на такий випадок Azure AD не надає. Крім того, загалом інфраструктура існуючих систем копанії гібридна, тому накладаються додаткові обмеження та складності. Що робити? Шукати workaround звичайно!

І буде він такий:

Серед Audit Activity Logs є магічний підтип, що має назву «Synchronization Rule Action», що використовується AD Connect Sync сервісом для синхронізації внутрішнього стану AD. Якщо коротко, то логи такого типу надають інформацію про зміни ресурсів, зокрема користувачів, груп та ін. з включенням полів, інформації, що змінилася. Його і використаємо.

Рекомендація: прилади json-структури івентів для повного списку полів Microsoft у документації надає частково, для окремих підтипів та випадків. Тому аби зберегти час, візуалізуйте дані, що ви уже заімпортили до Evet Hub-у або іншого сховища. У нашому випадку виглядатиме це наступним чином:

Переходимо до хабу з стрімом Audit Logs > Process data > Real time insights from events

Після того як ваші дані успішло завантажаться у вікно preview, натиснемо Download sample data. Потрібний івент Sync Action має наступну структуру:

{
        "time": "2022-08-18T09:34:35.7071398Z",
        "resourceId": "************************************",
        "operationName": "Synchronization rule action",
        "operationVersion": "0.0",
        "category": "AuditLogs",
        "tenantId": "****************************************",
        "resultSignature": "None",
        "resultDescription": "**************************************",
        "durationMs": 0,
        "correlationId": "**************************************",
        "identity": "Azure AD Cloud Sync",
        "Level": 4,
        "properties": {
          "id": "**************************************",
          "category": "ProvisioningManagement",
          "correlationId": "**************************************",
          "result": "success",
          "resultReason": "**************************************",
          "activityDisplayName": "Synchronization rule action",
          "activityDateTime": "2022-08-18T09:34:35.7071398",
          "loggedByService": "Account Provisioning",
          "operationType": "",
          "userAgent": null,
          "initiatedBy": {
            "app": {
              "appId": null,
              "displayName": "Azure AD Cloud Sync",
              "servicePrincipalId": null,
              "servicePrincipalName": null
            }
          },
          "targetResources": [
            {
              "id": "**************************************",
              "displayName": "SoSafe SCIM",
              "type": "ServicePrincipal",
              "modifiedProperties": [
                 ...
                {
                  "displayName": "accountEnabled",
                  "oldValue": null,
                  "newValue": "\"False\""
                },
               
                ...
              ],
              ...
            },
          ...
        }
      },

Такий спосіб гарантує наочніть та вбереже вас від годин пошуку в офіційній документації.

Корисні посилання, які вам можуть знадобитися:

Інфраструктура для фільтрації

Як працюватиме?

  1. Audit Logs івенти з Azure AD приходять до вхідного Event Hub.
  2. Спрацьовує Azure Function, типу тригер, що фільтрує отриманні записи та відправляє їх до Output Event Hub. Івенти, що не задовольняють критерій, ігноруються.
  3. Якщо Azure function з попереднього кроку спрацьовує з помилкою при фільтруванні чи збереженні даних у хаб, то вона намагаться записати сумнівний івент до Deadletter Event Hub. Для статистики.
  4. Після того, як івети надходять до Event Hub № 2, спрацьовує ще один тригер, що власне модифікує дані та робить запит до API нашої target-системи.

За основу взято схожий тест-case з прикладів Serverless filtering архітектур в документації Мікрософту.

Чому саме тригери

Безпечність. Механізм тригерів в Azure використовує System Identity аунтефікацію, що означає виклик serverless-функцій відбувається за внутрішнім ідентифікатором сервісами Azure. Детальніше про identity systems Azure можна почитати тут.

Шаблонізація. Про тригери написано достатньо літератури. Як в офійній документації, так і у статтях на медіумі. Зокрема, для нашого випадку ідеально підходить стаття у двох частинах від Microsoft:

  1. Azure Event Hub тригер для Azure functions (перехоплюємо дані з івент хабу до фільтруючої функції).
  2. Azure Event Hubs output binding for Azure Functions (зберігаємо фільтрованні дані у наступний хаб).

Реалізація проєкту на мові Node.js

І перше питання: «чому Node.js?». Було б гарно сказати, що ця мова ідеально підходить до подібного класу задач, що вона добре оптимізована під serverless і таке інше. Проте це питання дискусійне. Тому, аби зупинити цей, без сумнівів цікавий, диспут поки можна (читай як «поки не почалося») уточню: мова вибрана винятково з огляду на зручність для автора і суто суб’єктивний смак. Ну от і все, крапки розставлені.

Структура проєкту

За рекомендацією офіційної документації проєкт складатеметься з трьох модулів. Дві ажур функції та shared модуль, що класично зберігатиме спільні допоміжні функції.

Почнемо з функції для фільтрації івентів: events-ad-filter.

У реалізації самого фільру нічого особливого немає, ми перевірятимемо тип події, що потрапила до хабу, і якщо подія співпадає з бажаною, виконаємо додаткову перевірку, що властивість ‘accountEnabled’ отримала значення false при останній синхронізації з AD:

const EVENT_TYPE_SYNC = 'synchronization rule action';
 
export default function (context, eventHubMessages) {
    context.bindings.outputEventHubMessage = [];
    ...
    eventHubMessages
        .forEach((message) => {
 
            if (EVENT_TYPE_SYNC == message.records[0].operationName.toLowerCase()) {
                const isEnabled = message.records[0]
                    .properties
                    .targetResources[0]
                    .modifiedProperties
                    .find(prop => prop.displayName == 'accountEnabled');
               
                if (isEnabled && isEnabled.newValue == '"False"') {
                    context.bindings.outputEventHubMessage.push(JSON.stringify(message));
                }
            }
 
        });
    ...
}

Набагато цікавіше виглядає файл налаштувань функції для Azure-сервісів. Для node.js застосунків конфігурація зберігається у файлі func.json і саме тут ми зазначаємо, звідки приходять та куди зберігаються івенти:

{
  "bindings": [
    {
      "type": "eventHubTrigger",
      // ім'я змінної що ми використовували як вхідний параметер функції
      // саме під таким ім'ям Azure відправить нам дані подій
      "name": "eventHubMessages",
      // цим bind-інгом ми визначаємо те, як дані приходят до тригеру
      "direction": "in",
      // у даному випадку це івент хаб
      "eventHubName": "grizik-logs",
      // сonnection string можна отримати у меню Connect на сторінці хабу
      // ми використовуватимо її для авторизації
      "connection": "************************",
      // таким чином ми зазначаємо, що готові обробляти одразу декільла записів з хабу
      "cardinality": "many",
      "consumerGroup": "$Default"
    },
    {
      "type": "eventHub",
      // ім'я під яким ми відправляємо дані
      "name": "outputEventHubMessage",
      //  ім'я хабу, де зберігатимуться відсортовані івенти
      "eventHubName": "filtered-events-on-users",
      "connection": "********************************************",
      // зазначаємо, що це саме "вихід" для відфільтрованих записів
      "direction": "out"
  }
  ]
}

Наступний тригер: функція інтеграціїї з target-системою. Реалізація варіюється в залежності від власне системи. Тому в цій статті приділяти додаткову увагу реалізації конектора ми не будемо.

Конфігурація third-party-integration буде така:

{
  "bindings": [
    {
      "type": "eventHubTrigger",
      "name": "eventHubMessage",
      "direction": "in",
      "eventHubName": "filtered-events-on-users",
      "connection": "*************************************",
      // ми готові обробити лише один івент цією фунцією
      "cardinality": "one",
      "consumerGroup": "$Default"
    }
  ]
}

Тут нам вже не потрібно зазначати вихідний біндінг, оскільки відправку запитів ми здійснюємо в коді функції. Тому додаємо лише «in» напрям комунікації з ажуром.

Ще однією віддмінністю є те, що оскільки кількість відфільтрованих івентів значно менша, ми можемо обробляти їх по одинці. Так, навантаження буде розподіляти сам Azure Function App Scale Controller. Детальніше про цей процес можна почитати тут. Такий підхід має назву Event-Driven Scalling, та є ще однією варіацією на тему pay-as-you-go.

Гарненька діаграма з офіційної документації

Підсумки

На цьому наш кейс підійшов до кінця. Що ми маємо? Результати радують око: Павло успішно видалений звідусюди, наш workaround працює у фоновому режимі та красиво логує аналітику в Event Hub. А target-система продовжує радісно жити, і ні сном, ні духом про SCIM інтеграції та ваші танці з бубном. Краса та й годі.

Щиро сподіваюсь, що ця стаття допоможе комусь зберегти нерви та декілька вільних вечорів. Буду дуже рада коментарям та пропозиціям, вашим кейсам з Azure AD.

👍ПодобаєтьсяСподобалось14
До обраногоВ обраному0
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

Дуже цікава стаття, хоч і про доволі специфічне. Дякую!

Дякую!

Підписатись на коментарі