"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.
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.
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)
Jetzt folgt ein Klick auf Backup und die zwei rote “403 - Forbidden” Fehlermeldungen fallen sofort ins Auge.
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.
Ganz unten findet sich auch der aufgerufene Request
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'
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.
Bonus
Beim Prüfen der ausgeführten REST Requests fiel mir folgender Request in Auge
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.