Contents

"Reverse engineering" the Azure REST API

The Azure REST API is well documented and thanks to the REST API browser you can quickly try something out. However, there are moments in the Azure portal that lead to puzzled faces and in these cases the “Developer tools” of your favorite browser help to shed light into the dark.

The scenario

A user with “Read Only” rights on the subscription should be able to see if and where a virtual machine is backed up. The user can see the virtual machine without problems, but after clicking on “Backup” the request to set up the backup appears.

/en/reverse-engineering-azure-rest-api/images/Enable-backup-Microsoft-Azure.png

So for the inclined user, this virtual machine is not backed up.

Search for solutions

More permissions

More is always better and “Read Only” is not enough for many services (e.g. storage accounts) to see more than just the resource. What is in the resource is invisible for the user.

So I added the user to the group “Backup Reader” and checked if the Backup Vault can be accessed.

/en/reverse-engineering-azure-rest-api/images/2018-07-23-19_42_30-Backup-Items-Azure-Virtual-Machine-Microsoft-Azure.png

Works fine … Clicked back on the Backup tab in the virtual machine and the same error message appears as before. Because with the Backup Vault, “Reader” is also sufficient to see the individual Protected Items. Back to the drawing board.

A look under the hood

Return to the overview page of the virtual machines and activate the Chrome “Developer tools” using “CTRL + SHIFT + I”.

Then switch to the “Network” page and record only XMLHttpRequest (XHR) under Filter. Also make sure that the log is not lost (Preserve log).

/en/reverse-engineering-azure-rest-api/images/2018-07-23-19_46_58-AQI-PRO-BackupVault-Microsoft-Azure.png

Now click on Backup and the two red “403 - Forbidden” error messages immediately catch your eye.

/en/reverse-engineering-azure-rest-api/images/2018-07-23-19_50_34-Enable-backup-Microsoft-Azure.png

You can find out more about this when you select the request in question. The headers show that, contrary to expectations, no GET was performed here, but a POST.

/en/reverse-engineering-azure-rest-api/images/2018-07-23-19_54_31-Enable-backup-Microsoft-Azure.png

At the very bottom you can also find the called request

/en/reverse-engineering-azure-rest-api/images/2018-07-23-20_49_42-AQIAD04-Microsoft-Azure.png

Request Header:
....
x-ms-path-query: /subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/providers/Microsoft.RecoveryServices/locations/westeurope/backupStatus?api-version=2016-06-01
Request Payload:
{
    "resourceId": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/RESOURCEGROUPNAME/providers/Microsoft.Compute/virtualMachines/VMNAME",
    "resourceType": "VM"
}

In " Preview" the parsed JSON output can be displayed and this gives even more information about the performed action “Microsoft.RecoveryServices/locations/backupStatus/action”.

{
    "error": {
        "code": "AuthorizationFailed",
        "message": "The client 'user@bader.cloud' with object id 'd73b4dd6-0666-449c-aad2-nnnnnnnnnn' does not have authorization to perform action 'Microsoft.RecoveryServices/locations/backupStatus/action' over scope '/subscriptions/9c80de97-daff-4246-aa54-nnnnnnnnn'."
    }
}

In Microsoft’s RBAC documentation, this action is described as “Check Backup Status for Recovery Services Vaults”.

The solution

Custom RBAC Role

The solution is quickly implemented. Since the RBAC documentation was very revealing, I create a separate role with the same rights as the Reader role plus the action Microsoft.RecoveryServices/locations/backupStatus/action.

{
    "Name": "Reader including BackupStatus",
    "IsCustom": true,
    "Description": "Lets you view everything including the backup status, but not make any changes.",
    "Actions": \[
        "\*/read",
        "Microsoft.RecoveryServices/locations/backupStatus/action"
    \],
    "AssignableScopes": \[
        "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
        "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
    \]
}

Now create the new role in the Azure Portal and assign it to the user / user group.

New-AzureRmRoleDefinition -InputFile '.\AzureReaderIncludingBackup.json'

/en/reverse-engineering-azure-rest-api/images/2018-07-23-20_18_09-Add-permissions-Microsoft-Azure.png

The result

Looks good. No more 403 error and the user sees immediately that his virtual machine is backed up. But he is not allowed to restore anything, because he lacks further rights.

/en/reverse-engineering-azure-rest-api/images/2018-07-23-20_18_50-AQIAD04-Microsoft-Azure.png

Bonus

While checking the executed REST requests, the following request caught my eye

https://management.azure.com/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/RESOURCEGROUPNAME/providers/Microsoft.RecoveryServices/vaults/BACKUPVAULTNAME/backupFabrics/Azure/protectionContainers/iaasvmcontainer;iaasvmcontainerv2;RESOURCEGROUPNAME;VMNAME/protectedItems/vm;iaasvmcontainerv2;RESOURCEGROUPNAME;VMNAME?$filter=expand%20eq%20%27ExtendedInfo%27&api-version=2017-07-01

The actual GET request is documented, but not the filter used

$filter=expand+eq+'ExtendedInfo'&api-version=2017-07-01

Through this, the normal response…

{
    "id": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/RESOURCEGROUPNAME/providers/Microsoft.RecoveryServices/vaults/BACKUPVAULTNAME/backupFabrics/Azure/protectionContainers/IaasVMContainer;iaasvmcontainerv2;RESOURCEGROUPNAME;VMNAME/protectedItems/VM;iaasvmcontainerv2;RESOURCEGROUPNAME;VMNAME",
    "name": "VM;iaasvmcontainerv2;RESOURCEGROUPNAME;VMNAME",
    "type": "Microsoft.RecoveryServices/vaults/backupFabrics/protectionContainers/protectedItems",
    "properties": {
        "friendlyName": "VMNAME",
        "virtualMachineId": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/RESOURCEGROUPNAME/providers/Microsoft.Compute/virtualMachines/VMNAME",
        "protectionStatus": "Healthy",
        "protectionState": "Protected",
        "healthStatus": "Passed",
        "healthDetails": \[
            {
                "code": 400239,
                "title": "IaasVmHealthGreenDefault",
                "message": "Backup pre-check status of this virtual machine is OK.",
                "recommendations": \[\]
            }
        \],
        "lastBackupStatus": "Completed",
        "lastBackupTime": "2018-07-23T02:36:32.2674417Z",
        "protectedItemDataId": "140738223649118",
        "protectedItemType": "Microsoft.Compute/virtualMachines",
        "backupManagementType": "AzureIaasVM",
        "workloadType": "VM",
        "containerName": "iaasvmcontainerv2;RESOURCEGROUPNAME;VMNAME",
        "sourceResourceId": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/RESOURCEGROUPNAME/providers/Microsoft.Compute/virtualMachines/VMNAME",
        "policyId": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/RESOURCEGROUPNAME/providers/Microsoft.RecoveryServices/vaults/BACKUPVAULTNAME/backupPolicies/AQI-PRO-DailyPolicy",
        "policyName": "DailyPolicy",
        "lastRecoveryPoint": "2018-07-23T02:36:35.6143732Z"
    }
}

… becomes a somewhat more detailed one.

{
    "id": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/RESOURCEGROUPNAME/providers/Microsoft.RecoveryServices/vaults/BACKUPVAULTNAME/backupFabrics/Azure/protectionContainers/IaasVMContainer;iaasvmcontainerv2;RESOURCEGROUPNAME;VMNAME/protectedItems/VM;iaasvmcontainerv2;RESOURCEGROUPNAME;VMNAME",
    "name": "VM;iaasvmcontainerv2;RESOURCEGROUPNAME;VMNAME",
    "type": "Microsoft.RecoveryServices/vaults/backupFabrics/protectionContainers/protectedItems",
    "properties": {
        "friendlyName": "VMNAME",
        "virtualMachineId": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/RESOURCEGROUPNAME/providers/Microsoft.Compute/virtualMachines/VMNAME",
        "protectionStatus": "Healthy",
        "protectionState": "Protected",
        "healthStatus": "Passed",
        "healthDetails": \[
            {
                "code": 400239,
                "title": "IaasVmHealthGreenDefault",
                "message": "Backup pre-check status of this virtual machine is OK.",
                "recommendations": \[\]
            }
        \],
        "lastBackupStatus": "Completed",
        "lastBackupTime": "2018-07-23T02:36:32.2674417Z",
        "protectedItemDataId": "140738223649118",
        "extendedInfo": {
            "oldestRecoveryPoint": "2018-07-17T02:39:57.9021763Z",
            "recoveryPointCount": 7,
            "policyInconsistent": false
        },
        "protectedItemType": "Microsoft.Compute/virtualMachines",
        "backupManagementType": "AzureIaasVM",
        "workloadType": "VM",
        "containerName": "iaasvmcontainerv2;RESOURCEGROUPNAME;VMNAME",
        "sourceResourceId": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/RESOURCEGROUPNAME/providers/Microsoft.Compute/virtualMachines/VMNAME",
        "policyId": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/RESOURCEGROUPNAME/providers/Microsoft.RecoveryServices/vaults/BACKUPVAULTNAME/backupPolicies/AQI-PRO-DailyPolicy",
        "policyName": "DailyPolicy",
        "lastRecoveryPoint": "2018-07-23T02:36:35.6143732Z"
    }
}

Helpful are e.g. the values oldestRecoveryPoint and recoveryPointCount to find orphaned and no longer needed protected items.