services | platforms | author |
---|---|---|
compute |
ruby |
vishrutshah |
This sample demonstrates how to create your Azure virtual machines with Managed Service Identity (MSI) using the Ruby SDK. This sample covers the two types of MSI scenarios:
- System Assigned Identity: the identity is created by ARM on VM creation/update
- User Assigned Identity: the identity is created and managed by the user, and assigned during VM creation/update
On this page
- Run this sample
- What is example.rb doing?
- Create a User Assigned Identity
- Create a virtual network
- Create a public IP address
- Create a network interface
- Create a virtual machine with system or user assigned identity
- Add MSI extension to the VM
- Create role assignment for the VM
- Verify MSI extension is running on VM by logging-in via ssh
- Delete the resources
-
If you don't already have it, install Ruby and the Ruby DevKit.
-
If you don't have bundler, install it.
gem install bundler
-
Clone the repository.
git clone https://github.com/Azure-Samples/compute-ruby-msi-vm.git
-
Install the dependencies using bundle.
cd compute-ruby-msi-vm bundle install
-
Create an Azure service principal either through
Azure CLI, PowerShell or the portal.
âť— NOTE âť— Please make sure to create an role assignment with
Owner
role. In case of insufficient permissions, role assignment may fail for this sample. -
Set the following environment variables using the information from the service principle that you created.
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}
[AZURE.NOTE] On Windows, use
set
instead ofexport
. -
Run the sample.
bundle exec ruby example.rb
This sample starts by setting up ResourceManagementClient, the resource provider clients, a resource group, a storage account and a user assigned identity if desired, using your subscription and credentials.
GROUP_NAME = 'azure-sample-compute-msi'
LOCATION = 'westcentralus'
USER_ASSIGNED_IDENTITY = true
SYSTEM_ASSIGNED_IDENTITY = false
subscription_id = ENV['AZURE_SUBSCRIPTION_ID'] || '11111111-1111-1111-1111-111111111111' # your Azure Subscription Id
options = {
tenant_id: ENV['AZURE_TENANT_ID'],
client_id: ENV['AZURE_CLIENT_ID'],
client_secret: ENV['AZURE_CLIENT_SECRET'],
subscription_id: subscription_id,
}
resource_client = Resources::Client.new(options)
network_client = Network::Client.new(options)
storage_client = Storage::Client.new(options)
compute_client = Compute::Client.new(options)
authorization_client = Authorization::Client.new(options)
#
# Managing resource groups
#
resource_group_params = resource_client.model_classes.resource_group.new.tap do |rg|
rg.location = LOCATION
end
# Create Resource group
puts 'Create Resource Group'
print_group resource_client.resource_groups.create_or_update(GROUP_NAME, resource_group_params)
# Create storage account
postfix = rand(1000)
storage_account_name = "rubystor#{postfix}"
puts "Creating a premium storage account with encryption off named #{storage_account_name} in resource group #{GROUP_NAME}"
storage_create_params = StorageModels::StorageAccountCreateParameters.new.tap do |account|
account.location = LOCATION
account.sku = StorageModels::Sku.new.tap do |sku|
sku.name = StorageModels::SkuName::PremiumLRS
sku.tier = StorageModels::SkuTier::Premium
end
account.kind = StorageModels::Kind::Storage
account.encryption = StorageModels::Encryption.new.tap do |encrypt|
encrypt.services = StorageModels::EncryptionServices.new.tap do |services|
services.blob = StorageModels::EncryptionService.new.tap do |service|
service.enabled = false
end
end
end
end
print_item storage_account = storage_client.storage_accounts.create(GROUP_NAME, storage_account_name, storage_create_params)
msi_client = Msi::Client.new(options)
identity_create_params = MsiModels::Identity.new.tap do |identity|
identity.location = LOCATION
end
print_item user_assigned_identity = msi_client.user_assigned_identities.create_or_update(GROUP_NAME, "myMsiIdentity", identity_create_params)
end
Now, we will create a virtual network and configure subnet for the virtual machine.
puts 'Creating a virtual network for the VM'
vnet_create_params = NetworkModels::VirtualNetwork.new.tap do |vnet|
vnet.location = LOCATION
vnet.address_space = NetworkModels::AddressSpace.new.tap do |addr_space|
addr_space.address_prefixes = ['10.0.0.0/16']
end
vnet.dhcp_options = NetworkModels::DhcpOptions.new.tap do |dhcp|
dhcp.dns_servers = ['8.8.8.8']
end
vnet.subnets = [
NetworkModels::Subnet.new.tap do |subnet|
subnet.name = 'rubySampleSubnet'
subnet.address_prefix = '10.0.0.0/24'
end
]
end
print_item vnet = network_client.virtual_networks.create_or_update(GROUP_NAME, 'sample-ruby-vnet', vnet_create_params)
Now, we will create a public IP address using dynamic IP allocation method for the Azure VM.
puts 'Creating a public IP address for the VM'
public_ip_params = NetworkModels::PublicIPAddress.new.tap do |ip|
ip.location = LOCATION
ip.public_ipallocation_method = NetworkModels::IPAllocationMethod::Dynamic
ip.dns_settings = NetworkModels::PublicIPAddressDnsSettings.new.tap do |dns|
dns.domain_name_label = 'msi-vm-domain-name-label'
end
end
print_item public_ip = network_client.public_ipaddresses.create_or_update(GROUP_NAME, 'sample-ruby-pubip', public_ip_params)
Now, we will create a network interface and assign the public ip address created in previous step.
print_item nic = network_client.network_interfaces.create_or_update(
GROUP_NAME,
"sample-ruby-nic-#{vm_name}",
NetworkModels::NetworkInterface.new.tap do |interface|
interface.location = LOCATION
interface.ip_configurations = [
NetworkModels::NetworkInterfaceIPConfiguration.new.tap do |nic_conf|
nic_conf.name = "sample-ruby-nic-#{vm_name}"
nic_conf.private_ipallocation_method = NetworkModels::IPAllocationMethod::Dynamic
nic_conf.subnet = subnet
nic_conf.public_ipaddress = public_ip
end
]
end
)
Now, we will set virtual machine parameters like OSProfile
, StorageProfile
, OSDisk
, HardwareProfile
& NetworkProfile
as usual. We will
also set the VirtualMachineIdentity
to be SystemAssigned
, UserAssigned
or SystemAssignedUserAssigned
for creating a managed service identity VM and then create the virtual machine.
puts 'Creating a Ubuntu 16.04.0-LTS Standard DS2 V2 virtual machine w/ a public IP'
vm_create_params = ComputeModels::VirtualMachine.new.tap do |vm|
vm.location = location
vm.os_profile = ComputeModels::OSProfile.new.tap do |os_profile|
os_profile.computer_name = vm_name
os_profile.admin_username = 'notAdmin'
os_profile.admin_password = 'Pa$$w0rd92'
end
vm.storage_profile = ComputeModels::StorageProfile.new.tap do |store_profile|
store_profile.image_reference = ComputeModels::ImageReference.new.tap do |ref|
ref.publisher = 'canonical'
ref.offer = 'UbuntuServer'
ref.sku = '16.04.0-LTS'
ref.version = 'latest'
end
store_profile.os_disk = ComputeModels::OSDisk.new.tap do |os_disk|
os_disk.name = "sample-os-disk-#{vm_name}"
os_disk.caching = ComputeModels::CachingTypes::None
os_disk.create_option = ComputeModels::DiskCreateOptionTypes::FromImage
os_disk.vhd = ComputeModels::VirtualHardDisk.new.tap do |vhd|
vhd.uri = "https://#{storage_acct.name}.blob.core.windows.net/rubycontainer/#{vm_name}.vhd"
end
end
end
vm.hardware_profile = ComputeModels::HardwareProfile.new.tap do |hardware|
hardware.vm_size = ComputeModels::VirtualMachineSizeTypes::StandardDS2V2
end
vm.network_profile = ComputeModels::NetworkProfile.new.tap do |net_profile|
net_profile.network_interfaces = [
ComputeModels::NetworkInterfaceReference.new.tap do |ref|
ref.id = nic.id
ref.primary = true
end
]
end
# Create User Assigned and/or System Assigned identity
puts 'Create an identity'
if USER_ASSIGNED_IDENTITY && SYSTEM_ASSIGNED_IDENTITY
vm.identity = ComputeModels::VirtualMachineIdentity.new.tap do |identity|
identity.type = ResourceIdentityType::SystemAssignedUserAssigned
identity.identity_ids = [user_assigned_identity.id]
end
elsif USER_ASSIGNED_IDENTITY
# Use User Assigned Identity for the VM
vm.identity = ComputeModels::VirtualMachineIdentity.new.tap do |identity|
identity.type = ResourceIdentityType::UserAssigned
identity.identity_ids = [user_assigned_identity.id]
end
elsif SYSTEM_ASSIGNED_IDENTITY
# Use System Assigned Identity for the VM
vm.identity = ComputeModels::VirtualMachineIdentity.new.tap do |identity|
identity.type = ResourceIdentityType::SystemAssigned
end
end
end
ssh_pub_location = File.expand_path('~/.ssh/id_rsa.pub')
if File.exists? ssh_pub_location
puts "Found SSH public key in #{ssh_pub_location}. Disabling password and enabling SSH authentication."
key_data = File.read(ssh_pub_location)
puts "Using public key: #{key_data}"
vm_create_params.os_profile.linux_configuration = ComputeModels::LinuxConfiguration.new.tap do |linux|
linux.disable_password_authentication = true
linux.ssh = ComputeModels::SshConfiguration.new.tap do |ssh_config|
ssh_config.public_keys = [
ComputeModels::SshPublicKey.new.tap do |pub_key|
pub_key.key_data = key_data
pub_key.path = '/home/notAdmin/.ssh/authorized_keys'
end
]
end
end
end
print_item vm = compute_client.virtual_machines.create_or_update(GROUP_NAME, "sample-ruby-vm-#{vm_name}", vm_create_params)
Now, we will add an VM extension ManagedIdentityExtensionForLinux
for Azure VM and configure it to run on port 50342
.
puts "Install Managed Service Identity Extension"
ext_name = 'msiextension'
vm_extension = ComputeModels::VirtualMachineExtension.new.tap do |extension|
extension.publisher = 'Microsoft.ManagedIdentity'
extension.virtual_machine_extension_type = 'ManagedIdentityExtensionForLinux'
extension.type_handler_version = '1.0'
extension.auto_upgrade_minor_version = true
extension.settings = Hash.new.tap do |settings|
settings['port'] = '50342'
end
extension.location = LOCATION
end
vm_ext = compute_client.virtual_machine_extensions.create_or_update(GROUP_NAME, "sample-ruby-vm-#{vm_name}", ext_name, vm_extension)
Now, we will retrieve the default rbac role named as Contributor
. To know more about the
default roles please visit built-in-roles.
puts "Getting the Role ID of Contributor of a Resource group: #{GROUP_NAME}"
role_name = 'Contributor'
roles = authorization_client.role_definitions.list(resource_group.id, "roleName eq '#{role_name}'")
contributor_role = roles.first
Now, we will assign Contributor
role at the resource group level to allow managing Azure resources inside
this resource group.
msi_accounts_to_assign = []
if SYSTEM_ASSIGNED_IDENTITY
msi_accounts_to_assign.push(vm.identity.principal_id)
end
if USER_ASSIGNED_IDENTITY
msi_accounts_to_assign.push(user_assigned_identity.principal_id)
end
puts 'Creating the role assignment for the VM'
role_assignment_params = AuthorizationModels::RoleAssignmentCreateParameters.new.tap do |role_param|
role_param.properties = AuthorizationModels::RoleAssignmentProperties.new.tap do |property|
property.principal_id = msi_identity
property.role_definition_id = contributor_role.id
end
end
authorization_client.role_assignments.create(resource_group.id, SecureRandom.uuid, role_assignment_params)
Once the Azure VM has been created, we will verify that MSI extension is running on this VM. Managed Service Identity extension will run on
localhost
and configured port, here 50342
. Follow example here to
find out the usage.
ssh -p 22 notAdmin@msi-vm-domain-name-label.westcentralus.cloudapp.azure.com
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 -
...
exit
Now, we will delete all the resources created using this example. Please comment this out to keep the resources alive in you Azure subscription.
resource_client.resource_groups.delete(GROUP_NAME)