Azure: add support for User-assigned Managed Identities

Facilitates means for passwordless use-cases in Azure.
Similar to iam-instance-profile in AWS.

Change-Id: I0de170161cef78acd8016a0f98cd5a91c1d4999e
This commit is contained in:
Zalan Blenessy 2024-03-08 15:22:09 +01:00 committed by James E. Blair
parent f4941d4f03
commit 50d0fb788c
6 changed files with 80 additions and 0 deletions

View File

@ -749,6 +749,33 @@ section of the configuration.
If given, the size of the operating system disk, in GiB.
.. attr:: user-assigned-identities
:type: dict
:default: None
`User-assigned Managed Identities`_ to assign to the VM.
Useful for giving access to services without needing any secrets.
.. attr:: name
:required:
:type: str
The name of the User-assigned Managed Identity.
.. attr:: resource-group
:type: str
:default: The provider's resource group
Overrides :attr:`providers.[azure].resource-group`.
For example:
.. code-block:: yaml
user-assigned-identities:
- name: myLocalIdentity
- name: myRemoteIdentity
resource-group: remote-rg
.. _`Azure CLI`: https://docs.microsoft.com/en-us/cli/azure/create-an-azure-service-principal-azure-cli?view=azure-cli-latest
@ -757,3 +784,5 @@ section of the configuration.
.. _`Azure User Data`: https://docs.microsoft.com/en-us/azure/virtual-machines/user-data
.. _`Azure Custom Data`: https://docs.microsoft.com/en-us/azure/virtual-machines/custom-data
.. _`User-assigned Managed Identities`: https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/qs-configure-rest-vm

View File

@ -22,6 +22,7 @@ import string
import cachetools.func
from urllib.parse import urlparse
from nodepool.driver.utils import QuotaInformation, RateLimiter
from nodepool.driver import statemachine
from nodepool import exceptions
@ -652,6 +653,23 @@ class AzureAdapter(statemachine.Adapter):
if label.user_data:
spec['properties']['userData'] = label.user_data
# build resource id for all configured User-assigned Identities
uai_resource_ids = set()
for uai in label.user_assigned_identities:
uai_rg_name = uai.get("resource-group", self.resource_group)
uai_url = self.azul.managed_identities.url(
resourceGroupName=uai_rg_name, resourceName=uai['name'])
uai_resource_ids.add(urlparse(uai_url).path)
# adding empty userAssignedIdentities is not allowed by Azure
if uai_resource_ids:
spec['identity'] = {
'type': 'UserAssigned',
'userAssignedIdentities': {
rid: {} for rid in uai_resource_ids
},
}
with self.rate_limiter:
self.log.debug(f"Creating VM {hostname}")
return self.azul.virtual_machines.create(

View File

@ -268,6 +268,11 @@ class AzureCloud:
providerId='Microsoft.Compute',
resource='skus',
apiVersion='2019-04-01')
self.managed_identities = AzureResourceProviderCRUD(
self,
providerId='Microsoft.ManagedIdentity',
resource='userAssignedIdentities',
apiVersion='2023-01-31')
def get(self, url, codes=[200]):
return self.request('GET', url, None, codes)

View File

@ -193,6 +193,8 @@ class AzureLabel(ConfigValue):
self.custom_data = self._encodeData(label.get('custom-data', None))
self.host_key_checking = self.pool.host_key_checking
self.volume_size = label.get('volume-size')
self.user_assigned_identities = label.get(
'user-assigned-identities', [])
def _encodeData(self, s):
if not s:
@ -205,6 +207,11 @@ class AzureLabel(ConfigValue):
v.Required('vm-size'): str,
}
user_assigned_identities = {
v.Required('name'): str,
'resource-group': str,
}
return {
v.Required('name'): str,
'cloud-image': str,
@ -215,6 +222,7 @@ class AzureLabel(ConfigValue):
'user-data': str,
'custom-data': str,
'volume-size': int,
'user-assigned-identities': [user_assigned_identities],
}

View File

@ -93,6 +93,10 @@ providers:
dynamic-tenant: "Tenant is {{request.tenant_name}}"
user-data: "This is the user data"
custom-data: "This is the custom data"
user-assigned-identities:
- name: localid
- name: otherid
resource-group: othergroup
- name: image-by-name
cloud-image: image-by-name
hardware-profile:

View File

@ -99,6 +99,22 @@ class TestDriverAzure(tests.DBTestCase):
self.fake_azure.crud['Microsoft.Compute/virtualMachines'].
requests[0]['properties']['userData'],
'VGhpcyBpcyB0aGUgdXNlciBkYXRh') # This is the user data
self.assertEqual(
self.fake_azure.crud['Microsoft.Compute/virtualMachines'].
requests[0]['identity'],
{
"type": "UserAssigned",
"userAssignedIdentities": {
f"/subscriptions/{self.fake_azure.subscription_id}/"
"resourceGroups/nodepool/"
"providers/Microsoft.ManagedIdentity/"
"userAssignedIdentities/localid": {},
f"/subscriptions/{self.fake_azure.subscription_id}/"
"resourceGroups/othergroup/"
"providers/Microsoft.ManagedIdentity/"
"userAssignedIdentities/otherid": {},
}
})
tags = (self.fake_azure.crud['Microsoft.Compute/virtualMachines'].
requests[0]['tags'])
self.assertEqual(tags.get('team'), 'DevOps')