Позбуваємось паролів в Azure: Managed Identity і Workload Identity
Привіт! Мене звати Богдан, і я Lead DevOps Engineer в Critical Services CoE, SoftServe.
На прикладі великої кількості проєктів я помітив, що використання паролів в конфігураціях вимагає налагодження багатьох додаткових процесів, без яких не можна бути впевненим в безпеці рішення: регулярна ротація паролів, шифрування паролів at rest і at transit, аудит доступу до паролів. Імплементація цих процесів може вимагати значної кількості ресурсів команди — і далеко не завжди вони є.
Альтернатива використання паролів в конфігураціях — passwordless рішення для аутентифікації сервісів. У своїй статті я поділюсь своїм досвідом з такими рішеннями в Azure: Managed Identity.
Також я огляну нове рішення для Azure Kubernetes Service — Workload Identity. Я не надаватиму конкретних інструкцій з використання, проте поясню, як вони працюють, чим відрізняються і допоможу зрозуміти, коли краще використовувати те чи інше рішення.
Ця стаття буде корисною для спеціалістів, які займаються розробкою чи розгортанням сервісів на продуктах Azure. Незалежно від того, чи ви новачок в цій галузі, чи досвідчений фахівець, у цій статті ви знайдете корисну інформацію та поради, які допоможуть підвищити безпеку вашого Azure середовища та спростити керування ним.
Виклики
Базу даних і вебсервіс, який з нею комунікує, можна знайти ледве не на кожному проєкті чи в продукті з клієнт-серверною архітектурою. Уявімо, що ми в команді, яка пише такий сервіс (Azure Web App + Azure Table Storage), і наша задача — розгорнути його в кількох екземплярах: dev, test, prod. В кожного середовища своя інфраструктура і конфігурація доступів для команд.
Для кожного середовища нам потрібно створити окремий storage access key, і записати його в конфіг Web App. Ось як така інфраструктура виглядатиме з terraform:
resource "azurerm_linux_web_app" "backend" { name = "${var.rg_name}-ba-app" resource_group_name = azurerm_resource_group.main.name location = var.location service_plan_id = azurerm_service_plan.backend.id site_config {} app_settings = { storage_account_name = azurerm_storage_account.backend.name storage_account_key = azurerm_storage_account.backend.primary_access_key } } resource "azurerm_storage_account" "backend" { name = "${replace(var.rg_name, "-", "")}basa" resource_group_name = azurerm_resource_group.main.name location = var.location account_tier = "Standard" account_replication_type = "LRS" }
Ключ сторедж-акаунту (а точніше, три ключі — для dev, test, і prod) варто буде ротувати з певною періодичністю. Якщо людина, яка мала можливість їх отримати, залишає команду — їх знову ж таки потрібно ротувати.
Коли сервіс один, а середовищ всього три — ці задачі займають не так багато часу, але ускладнімо задачу і уявімо, що продукт розрісся з одного до двох десятків сервісів. В такому випадку, загальна кількість ключів, за які ми відповідатимемо, зросте до 60!
Звісно, можна просто автоматизувати створення і ротацію сікретів, але чи потрібно автоматизовувати процес, якщо можна просто позбутись його?
Azure Managed Identity
Azure Managed Identity — це механізм в Azure, який значно спрощує життя, повністю забираючи необхідність використовувати паролі і ключі в конфігураціях. Цей механізм дозволяє створювати сервісні акаунти спеціального типу в Azure AD (Managed Identity) і асайнити їх на ресурси, наприклад, на Web App чи на віртуальну машину.
Коли така managed identity заасайнена на ресурс — цьому ресурсу стає доступний спеціальний сервісний ендпоінт, який повертає JWT токен для цієї айдентіті. Токен можна використовувати, щоб авторизуватись на будь-яких сервісах, які підтримують Azure AD.
Є два види managed identity — system-assigned і user-assigned. System-assigned — конфігурується як частина ресурсу, і ділить з ресурсом його життєвий цикл. User-assigned потрібно створювати як окремий ресурс, одну айдентіті можна асайнити на кілька ресурсів.
Ось як використання system-assigned managed identity виглядатиме для нашого сервісу:
- Почати потрібно з увімкнення managed identity на нашому web app. Ця операція створить сервісний акаунт, який буде використовуватись нашим застосунком для авторизації при підключенні до бази даних.
- Після створення сервісному акаунту (managed identity) потрібно дати права на читання і запис нашої бази даних.
- В наш сервіс, розгорнутий на Web App, необхідно додати підтримку авторизації з managed identity: тепер, замість того, щоб передавати користувача і пароль при підключенні до бази, сервісу потрібно отримувати токен з managed identity endpoint і передавати його. Для найпопулярніших мов програмування є бібліотеки від Microsoft (Mircosoft Authentification Library), які реалізують цю підтримку, і достатньо просто їх під’єднати і замінити конфігурацію для підключення до сторедж акаунту (ось приклад для C#).
З цими змінами, наш terraform код для розгортання web app виглядатиме так:
resource "azurerm_linux_web_app" "backend" { name = "${var.rg_name}-ba-app" resource_group_name = azurerm_resource_group.main.name location = var.location service_plan_id = azurerm_service_plan.backend.id site_config {} app_settings = { storage_account_name = azurerm_storage_account.backend.name } # Enable system-assigned managed identity on the web app identity { type = "SystemAssigned" } } # Enable web app managed identity to use storage account tables resource "azurerm_role_assignment" "backend_web_app_to_storage_account" { scope = azurerm_storage_account.backend.id role_definition_name = "Storage Table Data Contributor" principal_id = azurerm_linux_web_app.backend.identity.0.principal_id }
Storage account key більше немає в конфізі і, до речі, ми тепер можемо відключити авторизацію за допомогою нього на нашому сторедж-акаунті:
resource "azurerm_storage_account" "backend" { name = "${replace(var.rg_name, "-", "")}basa" resource_group_name = azurerm_resource_group.main.name location = var.location account_tier = "Standard" account_replication_type = "LRS" # Disable authorisation using storage account keys - # use Azure AD only shared_access_key_enabled = false }
Один ресурс може мати більше однієї заасайненої managed identity (наприклад, одночасно system-assigned і кілька різних user-assigned) — в такому випадку для отримання токена при запиті до managed identity endpoint потрібно вказувати client id тієї managed identity, яку ви хочете використовувати. Якщо ви використовуєте MSAL, то вам потрібно лише створити environment змінну «AZURE_CLIENT_ID» — бібліотека зчитає її значення і використовуватиме для отримання токена.
При додаванні підтримки managed identity у ваш сервіс, слід врахувати, що вона не працюватиме локально, на комп’ютерах розробників: сервісний ендпоінт, який повертає токен, доступний тільки для ресурсу, на якому сконфігурована айдентіті.
Гарні новини: MSAL для всіх мов програмування працює за однаковим алгоритмом. Функція, яку потрібно використовувати для отримання токена, почергово намагається його отримати різними способами, в заданому порядку.
Зі всіх опцій для локальної розробки підходять дві: Environment Credentials і Visual Studio Credentials. В першому випадку потрібно створювати Service Principal, конфігурувати для нього весь набір прав, які були б в managed identity, а розробникам потрібно створювати environment змінні з деталями service principal (так, в цьому випадку в нас з’являється пароль, який відомий всім членам команди, але це лише для development ресурсів).
Друга опція підходить тільки для .net розробників, але з нею нема потреби створювати додаткові акаунти — права на ресурси для розробки потрібно конфігурувати для AAD акаунтів розробників.
Managed Identity можна використовувати з будь-якими сервісами, які підтримують Azure AD авторизацію: CosmosDB, Storage, або навіть з вашим власним API.
AKS
Дивіться також DevOps Podcast 👇Managed Identity можна використовувати і з AKS. Найпростіша опція — скористатись kubelet identity. AKS використовує окремі айдентіті для control plane і kubelet (worker nodes) — їх потрібно конфігурувати при створенні кластера.
В розрізі ресурсів Azure, kubelet identity — це айдентіті, яка заасайнена на ресурси нод-пулу (скейл-сети або віртуальні машини в сервісній ресурсній групі кластера). Вона в першу чергу призначена для інтеграції з Azure Container Registry і для того, щоб дозволити AKS аддонам операції над ресурсами Azure (наприклад, щоб дозволити контролеру CSI драйвера керувати сторедж-акаунтом для динамічного провіжинінгу), але насправді, будь-який ворклоад, запущений в кластері, може звернутись до managed identity ендпоінту і отримати токен для kubelet identity.
Тобто достатньо наконфігурувати рол-асайменти, і сервіси, задеплоєні в кластер, зможуть авторизуватись на потрібних вам ресурсах. Ось як виглядає terraform код, який розгортає таку інфраструктуру:
# Deploy Kubernetes cluster resource "azurerm_kubernetes_cluster" "main" { name = "${var.rg_name}-aks" location = var.location resource_group_name = azurerm_resource_group.main.name dns_prefix = "${var.rg_name}-aks" default_node_pool { name = "default" node_count = 1 vm_size = "Standard_D2_v2" } # This block is required in order to enable managed identity for master # node. Kubelet managed identity is created automatically, regardless of # which identity is used for master. identity { type = "SystemAssigned" } } # Enable AKS kubelet identity to use storage account tables resource "azurerm_role_assignment" "backend_web_app_to_storage_account" { scope = azurerm_storage_account.backend.id role_definition_name = "Storage Table Data Contributor" principal_id = azurerm_kubernetes_cluster.main.kubelet_identity[0].object_id }
Недолік такого підходу — одна айдентіті доступна для всіх сервісів, розгорнутих в кластері (і в цієї айдентіті може бути доволі багато прав). Саме через це я не рекомендую використовувати такий підхід в продакшн.
Свого часу, проблему з гранулярністю доступів вирішила AAD Pod Identity. Вона складалась з двох компонентів — Managed Identity Controller (MIC) і Node Managed Identity (NMI).
MIC дозволяв створювати кастомні ресурси, за допомогою яких можна було асайнити айдентіті на поди. Він використовував kubelet identity, і асайнив айдентіті лише на ті віртуальні машини чи скейл-сети, де був запущений под, який їх використовував.
NMI — це Daemon Set, який за допомогою IP tables редіректив запити подів до managed identity ендпоінту на себе, і повертав поду токен тільки тих айдентіті, які були заасайнені на под кастомні ресурси. Зараз AAD Pod Identity вже застаріла, їй на заміну прийшла Azure Workload Identity.
Azure Workload Identity
Workload Identity — це новий, рекомендований Microsoft підхід для безпарольних конфігурацій в Kubernetes. Він базується на OIDC Federation, і дозволяє сервісам, розгорнутим в практично будь-якому Kubernetes-кластері, авторизуватись на ресурсах, що підтримують Azure AD.
Workload Identity вирішує ряд проблем, які виникали з AAD Pod Identity:
- AAD Pod Identity працював тільки на Linux воркер нодах (оскільки NMI використовував iptables для емуляції managed identity endpoint).
- AAD Pod Identity працював тільки в AKS. Workload Identity використовує OpenID Connect — протокол нативно підтримується Kubernetes, тому цей механізм можна використати практично на будь-якому кластері.
- В AAD Pod Identity були проблеми з масштабуванням — авторизація не працювала, доки на ноді кластеру не підніметься NMI, а в умовах, коли кількість нод швидко росте, часто це може бути пізніше, ніж запуститься сам сервіс, якому потрібно авторизуватись.
З Workload Identity зручно ознайомлюватись на прикладі. Уявімо, що ми мігрували наш вебсервіс в AKS. В такому випадку, нам потрібно виконати наступні кроки, щоб почати її використовувати для авторизації на базі даних:
1. Спершу нам потрібно переконатись, що наш AKS-кластер правильно сконфігурований: потрібно переконатись, що при створенні були включені фічі «oids issuer» і «workload identity». Включення першої фічі робить доступним ендпоінт з публічними ключами, які використовуються для підписання токенів для Kubernetes Service Accounts: маючи ці ключі, будь-хто може верифікувати токен-сервіс акаунта.
Включення другої фічі додає в наш кластер mutating admission webhook — окремий компонент, який відслідковує створення нових подів і модифікує їх: додає окремий volume і volume mount з токеном Kubernetes Service Account, який заасайнений на цей под. Оновлений terraform код для деплоя AKS:
# Deploy Kubernetes cluster resource "azurerm_kubernetes_cluster" "main" { name = "${var.rg_name}-aks" location = var.location resource_group_name = azurerm_resource_group.main.name dns_prefix = "${var.rg_name}-aks" default_node_pool { name = "default" node_count = 1 vm_size = “Standard_D2_v2" } # This block is required in order to enable managed identity for master # node. Kubelet managed identity is created automatically, regardless of # which identity is used for master. identity { type = "SystemAssigned" } # Enable workload identity support for the AKS cluster: oidc_issuer_enabled = true workload_identity_enabled = true }
2. Наступний крок — нам потрібно створити (чи перевикористати існуючу) User-Assigned Managed Identity, і конфігурувати права для неї:
# Create a user-assigned managed identity, which will be used by # our application resource "azurerm_user_assigned_identity" "backend_identity" { resource_group_name = azurerm_resource_group.main.name location = var.location name = "${var.rg_name}-ba-uami" } # Enable managed identity we created to use storage account tables resource "azurerm_role_assignment" "backend_web_app_to_storage_account" { scope = azurerm_storage_account.backend.id role_definition_name = "Storage Table Data Contributor" principal_id = azurerm_user_assigned_identity.backend_identity.principal_id }
3. Далі, потрібно створити deployment i service account для нашого вебсервісу:
--- apiVersion: v1 kind: ServiceAccount metadata: name: backend-app namespace: default --- apiVersion: v1 kind: Pod metadata: name: backend-app namespace: default labels: azure.workload.identity/use: "true" spec: serviceAccountName: backend-app containers: ...
4. Наступний крок — створити OIDC Federation Credentials: повідомити Azure AD, що токени нашого kubernetes service account можуть бути обміняні на JWT токени managed identity, який ми створили раніше.
# Map kubernetes service account, which will be used by our application # to the managed identity we created and authorized to use our backend storage resource "azurerm_federated_identity_credential" "backend_identity" { name = "${var.rg_name}-ba-federated-credentials" resource_group_name = azurerm_resource_group.main.name audience = ["api://AzureADTokenExchange"] # This one is static issuer = azurerm_kubernetes_cluster.main.oidc_issuer_url parent_id = azurerm_user_assigned_identity.backend_identity.id subject = "system:serviceaccount:default:backend-app" }
5. Останній крок — оновити бібліотеку нашого сервісу до версії, яка підтримує такий тип авторизації.
Після цих кроків, коли ми передеплоїмо сервіс в кластер:
- Створиться kubernetes service account.
- При створенні подів для нашого сервісу admission webhook модифікує їх, і додасть volume і volume mount, зробивши токен service account доступним нашому сервісу.
- При спробі підключення до бази даних, наш сервіс викличе функцію MSAL для отримання токена. Функція перевірятиме доступні способи отримання токена, і зупиниться на механізмі workload identity.
- Функція зчитає токен kubernetes service account з volume, який був доданий при створенні поду admission webhook, і зробить запит до Azure AD, щоб обміняти цей токен на токен, який можна використати для авторизації в базі даних.
- Azure AD з’ясує з токена, якому сервісному акаунту він належить, знайде відповідні OIDC federated credentials, за допомогою них валідує токен сервісного акаунта і, якщо він валідний, поверне токен для managed identity, яка використовувалась в цих federated credentials.
- Наш сервіс отримає цей токен і з його допомогою авторизується при підключенні до бази даних.
Цей механізм федерації можна використовувати не тільки з Kubernetes: наприклад, вже зараз можете відмовитись від зберігання сікретів для Azure в gthub actions, і скоро можна буде в Azure DevOps.
Висновок
Завдяки Managed Identity i Workload Identity в Azure доволі просто практично повністю відмовитись від використання паролів в інфраструктурі. Все, що для цього потрібно — внести мінімальні зміни в сервіс, який потрібно розгортати, та в інфраструктуру.
Не гайте часу і спробуйте інструмент, який підходить для вашої інфраструктури:
- Azure Workload Identity;
- Azure Managed Identity;
- Повні приклади коду на github.
7 коментарів
Додати коментар Підписатись на коментаріВідписатись від коментарів