Create a VM with MSI authentication enabled

Laurent Mazuel 提供
上次更新时间:2017/9/13
在 GitHub 上编辑

This sample explains how to create a VM with Managed Service Identity enabled.

On this page

Run this sample

  1. If you don't already have it, install Python.

  2. We recommend to use a virtual environnement to run this example, but it's not mandatory. You can initialize a virtualenv this way:

    pip install virtualenv
    virtualenv mytestenv
    cd mytestenv
    source bin/activate
    
  3. Clone the repository.

    git clone https://github.com/Azure-Samples/compute-python-msi-vm.git
    
  4. Install the dependencies using pip.

    cd compute-python-msi-vm
    pip install -r requirements.txt
    
  5. Create an Azure service principal either through Azure CLI, PowerShell or the portal.

Important note: to be able to run this sample, your Service Principal MUST have "Owner" role enabled, or at least the "Microsoft.Authorization/*/write" permission. Learn more about Built-in Role for Azure

  1. Export these environment variables into your current shell.

    export AZURE_TENANT_ID={your tenant id}
    export AZURE_CLIENT_ID={your client id}
    export AZURE_CLIENT_SECRET={your client secret}
    export AZURE_SUBSCRIPTION_ID={your subscription id}
    
  2. Run the sample.

    python example.py
    

What is example.py doing?

The sample creates a VM with MSI creation. Then assign permission to that token. Finally it installs the VM extension necessary to get this token from inside the VM. It starts by setting up several clients using your subscription and credentials.

import os
from azure.common.credentials import ServicePrincipalCredentials
from azure.mgmt.resource import ResourceManagementClient
from azure.mgmt.compute import ComputeManagementClient
from azure.mgmt.network import NetworkManagementClient
from azure.mgmt.authorization import AuthorizationManagementClient

subscription_id = os.environ.get(
    'AZURE_SUBSCRIPTION_ID',
    '11111111-1111-1111-1111-111111111111') # your Azure Subscription Id
credentials = ServicePrincipalCredentials(
    client_id=os.environ['AZURE_CLIENT_ID'],
    secret=os.environ['AZURE_CLIENT_SECRET'],
    tenant=os.environ['AZURE_TENANT_ID']
)
resource_client = ResourceManagementClient(credentials, subscription_id)
compute_client = ComputeManagementClient(credentials, subscription_id)
network_client = NetworkManagementClient(credentials, subscription_id)
authorization_client = AuthorizationManagementClient(credentials, subscription_id)

There are a couple of supporting functions (print_item and print_properties) that print a resource group and it's properties.

Preliminary operations

This example setup some preliminary components that are no the topic of this sample and do not differ from regular scenarios:

  • A Resource Group
  • A Virtual Network
  • A Subnet
  • A Public IP
  • A Network Interface

For details about creation of these components, you can refer to the generic samples:

Create a VM with MSI creation

During the creation of the VM, only one attribute is necessary to ask Azure to assign a MSI ID to the VM.

params_create = {
    'location': LOCATION,
    'os_profile': get_os_profile(),
    'hardware_profile': get_hardware_profile(),
    'network_profile': get_network_profile(nic.id),
    'storage_profile': get_storage_profile(),
    # Activate MSI on that VM
    'identity': {
        'type': ResourceIdentityType.system_assigned
    }
}

vm_poller = compute_client.virtual_machines.create_or_update(
    GROUP_NAME,
    VM_NAME,
    params_create,
)
vm_result = vm_poller.result()

Role assignement to the MSI credentials

By default, the MSI account created for that VM does not have any permissions and will be unable to do anything.

This section shows how to get the role id of the built-in role "Contributor" and to assign it with the scope "Resource Group" to the MSI account.

msi_principal_id = vm_result.identity.principal_id

# Get "Contributor" built-in role as a RoleDefinition object
role_name = 'Contributor'
roles = list(authorization_client.role_definitions.list(
    resource_group.id,
    filter="roleName eq '{}'".format(role_name)
))
assert len(roles) == 1
contributor_role = roles[0]

# Add RG scope to the MSI token
role_assignment = authorization_client.role_assignments.create(
    resource_group.id,
    uuid.uuid4(), # Role assignment random name
    {
        'role_definition_id': contributor_role.id,
        'principal_id': msi_principal_id
    }
)

Install MSI extension

A VM extension is needed to be able to get the token from inside the VM. This extension is just a simple localhost server on port 50342 that returns the token.

ext_type_name = 'ManagedIdentityExtensionForLinux'
ext_name = vm_result.name + ext_type_name
params_create = {
    'location': LOCATION,
    'publisher': 'Microsoft.ManagedIdentity',
    'virtual_machine_extension_type': ext_type_name,
    'type_handler_version': '1.0',
    'auto_upgrade_minor_version': True,
    'settings': {'port': 50342}, # Default port. You should NOT change it.
}
ext_poller = compute_client.virtual_machine_extensions.create_or_update(
    GROUP_NAME,
    vm_result.name,
    ext_name,
    params_create,
)
ext = ext_poller.result()

Usage

You can now connect to the VM and use the MSI credentials directly, without passing credentials to the VM.

More details on how to use MSI sith SDK can be found in the MSI usage sample

Once the Azure VM has been created, you can verify that MSI extension is running on this VM. Managed Service Identity extension will run on localhost and configured port, here 50342.

notAdmin@msi-vm:~$ netstat -tlnp
(Not all processes could be identified, non-owned process info
 will not be shown, you would have to be root to see it all.)
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
tcp        0      0 127.0.0.1:50342         0.0.0.0:*               LISTEN      -               
...            

Delete a resource group

delete_async_operation = client.resource_groups.delete('azure-sample-group')
delete_async_operation.wait()