blog.dsoderlund.consulting

Interacting with the Azure Rest API with Azure CLI

Azure Rest API with Azure CLI - az rest to the rescue

Too long; didn’t read:

- Point to Site VPN in Azure cannot offer DNS service for private DNS zones.
- You can deploy a virtual machine or container on the network to act as a proxy / dns-forwarder
- Azure Container Instances is far from perfect
- When the automatic stuff fails for some reason, you might have get your hands dirty with Azure's Rest API

Background (It is always DNS)

Recently my team has been tasked to set up some infrastructure in Azure.

For the time being users will log on with point to site VPN and everything is working nicely.

Something that bothered me though was that while we could connect to resources on the private IP addresses the private DNS zone names would not be forwarded to clients over VPN.

The resources in Azure get their DNS resolution from Azure itself, us VPN clients are not so lucky. This is a well documented limitation of point to site VPN:

https://docs.microsoft.com/en-us/answers/questions/64223/issue-with-resolving-hostnames-while-connected-to.html

DNS forwarding for point to site clients

So basically, we have to deploy our own DNS-forwarder and point to it from the gateway. No biggie. Here is a nice implementation that is quick and easy to set up with Azure Container Instances.

https://github.com/whiteducksoftware/az-dns-forwarder

And then what happened was that Azure tried to help me and create the network profile that the Azure Container Instance will use, how nice of Azure.

The only problem was that since both the vnet and subnets have rather long names due to our naming convention, the resulting autogenerated name was longer than 80 characters and the deployment failed. No DNS-forwaring for us today!

So what we had to do was to override the automatic creation of the network profile for the Azure container instance.

Azure CLI and Powershell to the rescue

I will be mixing Azure cli and powershell, it just felt like the right thing to do.

Connect powershell to Azure and start browsing

While navigating Azure I like to use out-gridview, that way I can interact effortlessly with the intermediate objects and save my code for later.

#Gets the tenant I need from all my tenants
$tenant = get-aztenant | out-gridview -passthru 
Set-AzContext -Tenant $tenant.id

#Gets the subscription I need from all subscriptions for that tenant
$subscription = Get-AzSubscription -TenantId $tenant.id | out-gridview -PassThru
set-azcontext -Tenant $tenant.id -Subscription $subscription.Id

#Gets the resource group with the vnet I want to be using
$rg = Get-AzResourceGroup  | out-gridview -PassThru | Select -expandproperty ResourceGroupName

#now lets find the vnet
$vnet = Get-AzVirtualNetwork -resourcegroupname $rg | out-gridview -PassThru

#The network profile will live in a subnet
$snet = $vnet.Subnets | out-gridview -PassThru

Converting pscustomobject to json that Azure cli can use

Here is how I like to format my objects and transform them to json that az rest can use

Function Get-AzRestJson {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true,
            Position = 0,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true)]
        [ValidateNotNullOrEmpty()]
        [object]$InputObject
    )
    Return ConvertTo-Json -InputObject $InputObject -Compress -Depth 32 | Foreach-Object { $_ -replace '"', '\"' }
}

For example the object I will be deploying will have a structure which for me is easiest to build and look at with powershell as pscustomobject

$vnet and $snet are taken from the code about where we navigated from the tenant to the subscription, resource groups, virtual networks and their subnets.

$bodyobject = @{
    location   = $vnet.Location
    properties = @{
        containerNetworkInterfaceConfigurations = @(
            @{
                name       = "eth1"
                properties = @{
                    ipConfigurations = @(
                        @{
                            name       = "ipconfig1"
                            properties = @{
                                subnet = @{
                                    id = $snet.Id
                                }
                            }
                        }
                    )
                }
            }
        )
    }
}

Deploying the network profile with the Rest API

Allright, so now we can get our request body and put all this to work and finally enjoy our DNS-forwarding


$newprofilename = "aci-network-profile-$($snet.Name)" #80 chars is too long let us just use the subnet name
$apiversion = '2020-11-01'
$requestbody = $bodyobject | Get-AzRestJson #using the function we defined earlier to format correctly
$requestheaders = @{"Content-Type" = "application/json" } | Get-AzRestJson

#The full path to the resource we want to interact with
$requesturi = "/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Network/networkProfiles/{2}?api-version={3}" -f $subscription.id, $rg, $newprofilename, $apiversion

#connect with az cli
az account set --subscription $subscription.Id 

#create network profile (put)
az rest --method put --url $requesturi --body $requestbody --headers $requestheaders

You can of course use the rest api to search (get) and remove (delete) too!

#look for network profile
az rest --method get --url $requesturi

#delete network profile
az rest --method delete --url $requesturi

Finally, where were we?

Now that I have a network profile for my container instance that doesn’t fail validation (thanks Microsoft), I can finally do the comparitably trivial task of spinning up the container.

#Region create the container instance
# source: https://github.com/whiteducksoftware/az-dns-forwarder
# iwr https://raw.githubusercontent.com/whiteducksoftware/az-dns-forwarder/master/LICENSE | select -expand content
$imageref = "ghcr.io/whiteducksoftware/az-dns-forwarder/az-dns-forwarder@sha256:8671e086b0be1b7e43e9feebba833ff9025852f3b45570cb13d1d5bfc39f12c5"
#docker pull $imageref #test url for image

az container create `
    --resource-group $rg `
    --name dns-forwarder `
    --image $imageref `
    --cpu 1 `
    --memory 0.5 `
    --restart-policy always `
    --vnet $vnet.Name `
    --subnet $snet.Name `
    --location $vnet.Location `
    --os-type Linux `
    --port 53 `
    --protocol UDP `
    --network-profile $newprofilename
#EndRegion

Look at that little container go!

Finishing up dns-forwarding for the clients

Lastly I needed to set the DNS server for the vnet with the gateway to the container IP.

We have name resolution

After my vm restarted and I reconnected VPN I got the name resolution to work.