Inhalt

"Reverse engineering" der Azure REST API

Die Azure REST API ist grundsätzlich gut dokumentiert und dank des REST API Browsers lässt sich auch auf die schnelle etwas ausprobieren. Jedoch gibt es Momente im Azure Portal die zu verwunderten Gesichtern führen und in diesen Fällen helfen die “Developer tools” von Chrome um Licht ins Dunkel zu bringen.

Das Szenario

Einem Benutzer mit “Read Only” Rechten auf der Subscription soll es möglich sein zu sehen ob und wohin eine virtuelle Maschine gesichert wird. Die virtuelle Maschine kann der Benutzer noch ohne Probleme sehen, jedoch erscheint nach dem Klick auf “Backup” die Aufforderung dieses einzurichten.

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

Für den geneigten Anwender wird also diese virtuelle Maschine nicht gesichert.

Lösungssuche

Mehr Berechtigungen

Mehr ist immer besser und “Read Only” reicht bei vielen Diensten (z.B. Storage Accounts) nicht aus um mehr als nur die Ressource zu sehen. Was sich in der Ressource befindet ist für den Benutzer unsichtbar.

Also kurzerhand den Benutzer in die Gruppe “Backup Reader” aufgenommen und geprüft ob man auf den Backup Vault zugreifen kann.

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

Funktioniert einwandfrei … zurück bei der virtuellen Maschine auf den Backup Reiter geklickt und es erscheint dieselbe Fehlermeldung wie zuvor. Denn beim Backup Vault reicht auch “Reader” um die einzelnen Protected Items zu erkennen. Zurück ans Reißbrett.

Der Blick unter die Haube

Auf die Übersichtsseite der virtuellen Maschinen zurück und mittels “STRG + SHIFT + I” die Chrome “Developer tools” aktivieren.

Dort dann auf die Seite “Network” wechseln und bei Filter nur XMLHttpRequest (XHR) mitschneiden. Außerdem darauf achten, dass das Log nicht verloren geht (Preserve log)

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

Jetzt folgt ein Klick auf Backup und die zwei rote “403 - Forbidden” Fehlermeldungen fallen sofort ins Auge.

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

Genaueres erfährt man, wenn der betreffenen Request ausgewählt wird. Die Header zeigen an  das hier, anders als erwartet, kein GET durchgeführt wurde, sondern ein POST.

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

Ganz unten findet sich auch der aufgerufene Request

/reverse-engineering-der-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"
}

Unter “Preview” lässt sich sich der geparsten JSON Output anzeigen und dieser gibt noch mehr Aufschluss über die durchgeführte Aktion “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 der RBAC Dokumentation von Microsoft findet sich diese Aktion mit der Beschreibung “Check Backup Status for Recovery Services Vaults”

Die Lösung

Custom RBAC Role

Die Lösung ist schnell implementiert. Da die RBAC Dokumentation sehr aufschlussreich war, erstelle ich eine eigene Rolle mit denselben Rechten wie die Reader Rolle plus der Aktion 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"
    \]
}

Jetzt noch die neue Rolle im Azure Portal erstellen und dem Benutzer / der Benutzergruppe zuweisen.

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

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

Das Ergebnis

Kann sich sehen lassen. Kein 403 Fehler mehr und der Benutzer sieht sofort das seine virtuelle Maschine gesichert wird. Wiederherstellen darf er aber nichts, dazu fehlen Ihm weitere Rechte.

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

Bonus

Beim Prüfen der ausgeführten REST Requests fiel mir folgender Request in Auge

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

Der eigentliche GET Request ist dokumentiert, nicht jedoch der verwendete Filter

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

Durch diesen wird aus der normalen Antwort …

{
    "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"
    }
}

… eine etwas ausführlichere.

{
    "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"
    }
}

Hilfreich sind dabei z.B. die Werte oldestRecoveryPoint und recoveryPointCount um verwaiste und nicht mehr benötigte Protected Items zu finden.