Virtual Machine Metadata service

Getting useful information to IaaS

Prerequisites

For this solution it will be assumed that you have configured an Azure Account with sufficient privilege to query the state of virtual machines, networks, IP addresses and virtual networks. This account will also need to be configured to log in with a certificate, and have the certificate uploaded to the Functions application.

A document describing how to do this will be written shortly.

Overview

The basic concept of this Function is to have a public IP address call the functions webhook. The Function will then determine whether the IP Address is an address that belongs to the subscriptions it has authority to query.

If the IP address does belong to a subscription, it will then collect relevant information and send it back to the client. If the address doesn't belong to a subscription. It will simply return a 'not found'

Getting Started

When you use the Function template HttpTrigger - PowerShell you will get the following script.


  $requestBody = Get-Content $req -Raw | ConvertFrom-Json
  $name = $requestBody.name
  if ($req_query_name)
  {
      $name = $req_query_name
  }
  Out-File -Encoding Ascii $res -inputObject "Hello $name"

Obtaining incoming IP

In this first instance nothing is being passed to the function so we don't need to any of the input variables. All we need to do is find the address of the incoming request. So we can start with


$incomingIp = (((Get-Variable -Name "REQ_HEADERS_X-FORWARDED-FOR").value) -split ':')[0]
$incomingIp | convertto-json | Out-File -Encoding Ascii $res

Searching Subscription for IP

The basic premise we are going to follow in finding machine metadata is to search the subscription for the Public IP Address, once this is found The associated network interface will be queried to discover the VM, subnet, and security groups. These will then be collated into a json document and returned.


function Query-Address
{
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory=$true)]
        $QueryAddress
    )
    $ipAddressFound = $false 
    $publicIpAddresses = Get-AzureRmPublicIpAddress
    foreach ($ipAddress in $publicIpAddresses)
    {
        if ($ipAddress.IpAddress -like $QueryAddress)
        {
            #if address exists in subscription, capture NetworkInterface 
            $queryinterface = ($ipaddress.IpConfiguration.id -split)
            return $queryinterface
        }
    }
    if (!$ipAddressFound)
    {
        throw "Address Not found" 
    }
}
The first task we have is to find whether the incoming IP address belongs to a public IP address within that subscription. To do this we will use Get-AzureRmPublicIpAddress to collect all addresses in use, and then iterate through them. Once one is found, the associated interface Id is returned.

Getting Network Interface

Once we have the interface, we will have a reference to all of the other resources we need to query to obtain the rest of the machine metadata. We start with the interface.


function Query-Interface
{
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory=$true)]
        $QueryInterface
    )
    Get-AzureRmNetworkInterface -Name ($QueryInterface -split '/')[8] -ResourceGroupName ($QueryInterface -split '/')[4]
}
This code simple returns the appropriate interface, taken from stripping the relevant parts from the Network Interface Id.

Getting Subnet

A lot of network configuration data is stored in the subnet object. Things like Security Groups and routing tables all have references in the subnet configuration If we need data for these things we need a reference to the subnet first.


function Get-Subnet
{
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory=$true)]
        $QueryInterface
    )
    $vnetName = (($QueryInterface.IpConfigurations[0].Subnet).id -split '/')[8]
    $vnetResourceGroup = (($QueryInterface.IpConfigurations[0].Subnet).id -split '/')[4]
    $virtualNetwork = Get-AzureRmVirtualNetwork -Name $vnetName -ResourceGroupName $vnetResourceGroup 
      
    $SubnetName =  (($QueryInterface.IpConfigurations[0].Subnet).id -split '/')[10]
    Get-AzureRmVirtualNetworkSubnetConfig -Name $SubnetName -VirtualNetwork $virtualNetwork 
}
In order to obtain the subnet information we need to pass a virtual network reference.

Getting Security Groups

There are two types of security group in Azure. One that attaches to a network interface, and one that attaches to a subnet. We want to return both of these, if they exist.


         function Get-SecurityGroup
{
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory=$true)]
        $QueryInterface,
        [Parameter(Mandatory=$true)]
        $QuerySubnet
    )
    $SecurityGroups =     if ($QueryInterface.NetworkSecurityGroup -ne $null) {
         $InterfaceSecurityGroup = Get-AzureRmNetworkSecurityGroup `
                                    -Name ($QueryInterface.NetworkSecurityGroup.Id -split '/')[8] `
                                    -ResourceGroupName ($QueryInterface.NetworkSecurityGroup.Id -split '/')[4] 

        $SecurityGroups.Add("Interface",$InterfaceSecurityGroup)
    }
        if ($QuerySubnet.NetworkSecurityGroup -ne $null) {
         $SubnetSecurityGroup = Get-AzureRmNetworkSecurityGroup `
                                    -Name ($subnet.NetworkSecurityGroup.Id -split '/')[8] `
                                    -ResourceGroupName ($subnet.NetworkSecurityGroup.Id -split '/')[4] 

        $SecurityGroups.Add("Subnet",$SubnetSecurityGroup)
    }
    $SecurityGroups
}
Here we create a hash table so we can return both the interface security group and the subnet security group. In order to query the subnet and the interface for these values both need to be passed as parameters.

Get Virtual Machine data.

There are two sets of virtual machine data we need to collect. Firstly the configuration data, containing network cards, disks, OS, and VM size. There is also the status information. Which contains information regarding the status of the virtual machine and its extensions.


function Get-VM
{
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory=$true)]
        $QueryInterface
    )

    $VM =     $status = Get-AzureRmVM -Status `
                -ResourceGroupName ($QueryInterface.VirtualMachine.Id -split '/')[4] `
                -Name ($QueryInterface.VirtualMachine.Id -split '/')[8]
    $Configuration = Get-AzureRmVM `

                -ResourceGroupName ($QueryInterface.VirtualMachine.Id -split '/')[4] `
                -Name ($QueryInterface.VirtualMachine.Id -split '/')[8]#

    $vm.Add("status",$status) 
    $vm.Add("configuration",$Configuration) 
    $vm 
}
As with the function for Security groups we pass the configuration data and the status information back inside a hashtable.

Orchestration

Some logic is needed to tie the funtions together and pass data between them.


try {
        $interface = Query-Address -QueryAddress $IPAddress -Verbose 
    } catch {
        break     
    }
$interface = Query-Interface -QueryInterface $interface 
$subnet = Get-Subnet -QueryInterface $interface 
$SecurityGroup = Get-SecurityGroup -QueryInterface $interface -QuerySubnet $subnet 
$vm = Get-VM -QueryInterface $interface 
$returnData = $returnData.Add("vm",$vm) 
$returnData.Add("subnet",$subnet) 
$returnData.Add("securityGroup",$SecurityGroup) 
$returnData |ConvertTo-Json -Depth 5 -Compress | Out-File -Encoding Ascii $res 
Here we pass the incoming IP address into the Query-Address function, if that throws an error because it doesn't have the address configured the script will break. Otherwise the script will continue gathering data.

Once the data is collected it is added to a Hashtable, converted to json and passed back to the client.