Mes premières Azure Policy

Ayant travaillé sur Microsoft Azure récemment et dans un besoin de gouvernance, j’ai mis en place quelques policy sur l’environnement Azure sur lequel j’opère actuellement. Je vais présenter dans cet article quelques policy permettant de contrôler divers éléments.

Cette première policy a été importée depuis GitHub. Elle permet de surveiller la présence d’un lock sur un Resource Group si celui-ci possède un tag renseigné à une valeur spécifique. Par exemple, on peut placer un tag ProtectedByLock à True pour indiquer à la policy que ce Resource Group doit être protégé par un lock. Si jamais le tag n’est pas présent ou que celui-ci n’est pas à True, alors la policy ne remontera rien si ce Resource Group n’est pas protégé par un lock.

{
    "mode": "All",
    "policyRule": {
      "if": {
        "allOf": [
          {
            "field": "type",
            "equals": "Microsoft.Resources/subscriptions/resourceGroups"
          },
          {
            "field": "[concat('tags[', parameters('tagName'), ']')]",
            "equals": "[parameters('tagValue')]"
          }
        ]
      },
      "then": {
        "effect": "auditIfNotExists",
        "details": {
          "type": "Microsoft.Authorization/locks",
          "existenceCondition": {
            "field": "Microsoft.Authorization/locks/level",
            "equals": "CanNotDelete"
          }
        }
      }
    },
    "parameters": {
      "tagName": {
        "type": "String",
        "metadata": {
          "displayName": "Tag Name",
          "description": "The tag name to audit against (i.e. Environment, CostCenter, etc.)"
        }
      },
      "tagValue": {
        "type": "String",
        "metadata": {
          "displayName": "Tag Value",
          "description": "Value of the tag to audit against (i.e. Prod/UAT/TEST, 12345, etc.)"
        }
      }
    }
  }

Cette policy permet de vérifier que les disques virtuels existants sont tous bien rattachés à une machine virtuelle.

{
    "mode": "All",
    "policyRule": {
      "if": {
        "allOf": [
          {
            "field": "type",
            "equals": "Microsoft.Compute/Disks"
          },
          {
            "anyOf": [
              {
                "not": {
                  "field": "Microsoft.Compute/Disks/managedBy",
                  "exists": "True"
                }
              }     
            ]
          }
        ]
      },
      "then": {
        "effect": "audit"
      }
    },
    "parameters": {}
  }

De manière similaire, cette policy permet de mettre en évidence la présence de NIC orphelines.

{
  "mode": "All",
  "policyRule": {
    "if": {
      "allOf": [
        {
          "field": "type",
          "equals": "Microsoft.Network/networkInterfaces"
        },
        {
          "anyOf": [
            {
              "not": {
                "field": "Microsoft.Network/networkInterfaces/VirtualMachine",
                "exists": "True"
              }
            }
          ]
        }
      ]
    },
    "then": {
      "effect": "audit"
    }
  },
  "parameters": {}
}

Cette dernière policy permet de vérifier que les machines n’ayant pas un tag Backup à No sont bien sauvegardées par Azure Backup. Ici aussi, il est possible d’adapter le contrôle en fonction du tag et de la valeur attendue pour mettre en alerte les irrégularités.

{
    "mode": "Indexed",
    "policyRule": {
      "if": {
        "allOf": [
          {
            "field": "type",
            "equals": "Microsoft.Compute/virtualMachines"
          },
          {
            "anyOf": [
              {
                "not": {
                  "field": "[concat('tags[', parameters('TagName'), ']')]",
                  "equals": "[parameters('TagValue')]"
                }
              }
            ]
          }
        ]
      },
      "then": {
        "effect": "[parameters('effect')]",
        "details": {
          "type": "Microsoft.RecoveryServices/backupprotecteditems"
        }
      }
    },
    "parameters": {
      "effect": {
        "type": "String",
        "metadata": {
          "displayName": "Effect",
          "description": "Enable or disable the execution of the policy"
        },
        "allowedValues": [
          "AuditIfNotExists",
          "Disabled"
        ],
        "defaultValue": "AuditIfNotExists"
      },
      "TagValue": {
        "type": "String",
        "metadata": {
          "displayName": "Tag Value",
          "description": "Tag Value (No)"
        },
        "allowedValues": [
          "No"
        ],
        "defaultValue": "No"
      },
      "TagName": {
        "type": "String",
        "metadata": {
          "displayName": "Tag Name",
          "description": "Tag Name (Backup)"
        },
        "allowedValues": [
          "Backup"
        ],
        "defaultValue": "Backup"
      }
    }
  }

Powershell : expiration des secrets et certificats des applications Azure ActiveDirectory

Ce script permet d’afficher la liste des App Registrations Azure ActiveDirectory ayant un ou des secrets ou certificats expirant à 90 jours, 30 jours ou déjà expirés.

Il est bien évidemment possible d’adapter le script pour le faire tourner dans un runbook Azure ou sur un serveur de manière non-interactive en adaptant la partie authentification.

Connect-AzureAD
$AADAppsColl = Get-AzureADApplication -All:$true
foreach($AADApps in $AADAppsColl) {
    $AppID = $AADApps.AppID
    $AADApp = Get-AzureADApplication -Filter "AppID eq '$AppID'"
    $PassCreds = $AADApp.PasswordCredentials
    if ($null -ne $PassCreds) {
        foreach($PassCred in $PassCreds) {
            if($PassCred.EndDate -gt (Get-Date).AddDays(30) -and $PassCred.EndDate -le (Get-Date).AddDays(90)){
                Write-Host "AzureAD Application Name: $($AADApp.DisplayName)"
                Write-Host "KeyID: $($PassCred.KeyID)"
                Write-Host "Expires: $($PassCred.EndDate)" -ForegroundColor Green
                Write-Host `r
            }
            if($PassCred.EndDate -gt (Get-Date) -and $PassCred.EndDate -le (Get-Date).AddDays(30)) {
                Write-Host "AzureAD Application Name: $($AADApp.DisplayName)"
                Write-Host "KeyID: $($PassCred.KeyID)"
                Write-Host "Expires: $($PassCred.EndDate)" -ForegroundColor Orange
                Write-Host `r
            }
            if($PassCred.EndDate -le (Get-Date)) {
                Write-Host "AzureAD Application Name: $($AADApp.DisplayName)"
                Write-Host "KeyID: $($PassCred.KeyID)"
                Write-Host "Expired: $($PassCred.EndDate)" -ForegroundColor Red
                Write-Host `r
            }
        }
    }
    $KeyCreds = $AADApp.KeyCredentials
    if ($null -ne $KeyCreds) {
        foreach($KeyCred in $KeyCreds) {
            if($KeyCred.EndDate -gt (Get-Date).AddDays(30) -and $KeyCred.EndDate -le (Get-Date).AddDays(90)){
                Write-Host "AzureAD Application Name: $($AADApp.DisplayName)"
                Write-Host "Certificate ID: $($KeyCred.KeyID)"
                Write-Host "Expires: $($KeyCred.EndDate)" -ForegroundColor Green
                Write-Host `r
            }
            if($KeyCred.EndDate -gt (Get-Date) -and $KeyCred.EndDate -le (Get-Date).AddDays(30)) {
                Write-Host "AzureAD Application Name: $($AADApp.DisplayName)"
                Write-Host "Certificate ID: $($KeyCred.KeyID)"
                Write-Host "Expires: $($KeyCred.EndDate)" -ForegroundColor Orange
                Write-Host `r
            }
            if($KeyCred.EndDate -le (Get-Date)) {
                Write-Host "AzureAD Application Name: $($AADApp.DisplayName)"
                Write-Host "Certificate ID: $($KeyCred.KeyID)"
                Write-Host "Expired: $($KeyCred.EndDate)" -ForegroundColor Red
                Write-Host `r
            }
        }
    }
}

Une version commentée du script est disponible en téléchargement.

Robocopy KO sur partage Azure Files

Azure Files est une solution proposée par Microsoft via Azure permettant de créer des partages SMB liés à des Storage Account. Ainsi, il est possible de pousser ses répertoires partagés sans avoir de serveur de fichiers en tant que tel.

Je travaille actuellement sur un sujet portant sur le déploiement de plusieurs partages portés par Azure Files. Une fois la solution mise en place, ma tâche aura été de faire un robocopy des données locales vers le partage SMB porté par Azure Files. Cependant, en montant le partage sur le serveur de transfert avec un compte ayant les bons privilèges RBAC et NTFS, Robocopy se heurtait à un manque de permissions, y compris en mode backup :

2021/08/10 15:35:59 ERROR 5 (0x00000005) Copying NTFS Security to Destination Directory \\dundermifflin.inc\shares\iso\
Access is denied.

Voici plus ou moins à quoi ressemblait mon instruction Robocopy exécutée par Powershell.

Start-Process "robocopy.exe" -ArgumentList "$src $dst /S /E /COPY:DATSOU /PURGE /MIR /MT:4 /R:1 /W:1" 

Ce qui cause l’erreur de permissions est l’argument S de /copy, ce qui demande à Robocopy de conserver les permissions NTFS des fichiers lors du transfert.

Il faut savoir que les permissions sont habituellement découpées en 2 couches au niveau d’un partage : les privilèges SMB et NTFS. Les permissions sont indépendantes ; si sur un serveur managé il est possible d’intervenir sur les permissions SMB, ce n’est pas le cas avec Azure Files car l’accès est donné au service portant les partages, pas au serveur en lui-même. Par conséquent, les permissions sont redescendues via RBAC. Il existe des rôles Storage File Data SMB qui permettent de placer des droits en lecture et/ou en écriture sur les partages portés par Azure Files et qui positionnent ensuite au niveau SMB les permissions. Dans ce cas, mon compte ayant le rôle Storage File Data SMB Share Elevated Contributor était celui utilisé.

En fouinant un peu sur le net, je suis tombé sur cette question posée sur la section Q&A du site de Microsoft. Un utilisateur a été très récemment confronté au même problème. Cette erreur de permissions est due au fait que Robocopy a besoin de devenir propriétaire du fichier qu’il vient d’écrire à destination pour lui attribuer les permissions NTFS du fichier d’origine. Or, sur Azure Files, même avec un compte ayant les privilèges RBAC et NTFS au maximum, ce n’est pas possible. Robocopy va donc se heurter à un blocage et va renvoyer le message d’erreur plus haut.

En conséquence, pour contourner le problème qui est reconnu — et pour lequel Microsoft travaille sur une solution qui consisterait à donner à un compte un privilège RBAC permettant d’autoriser de devenir propriétaire d’un fichier — Microsoft conseille de monter le partage distant Azure Files avec un compte local d’administration qui lui, possède tous les droits.

Le partage doit donc être monté en tant que AZURE\NomDuStorageAccount ; le mot de passe est l’Access Key qui est générée et déclarée au niveau du Storage Account.

Au niveau du Storage Account, dans la section Security + networking du blade, il suffit de se rendre dans Access keys pour arriver sur cette page. La première clef est le mot de passe du compte local à utiliser. Ainsi, si le compte de stockage s’appelle storagedundermifflin, le partage doit être monté avec l’utilisateur AZURE\storagedundermifflin.

Ce compte possède tous les droits sur le partage et court-circuite les privilèges RBAC. En utilisant ce compte, Robocopy est donc capable de devenir propriétaire des fichiers copiés et d’y appliquer les permissions NTFS d’origine. En exécutant de nouveau le Robocopy, tout s’est bien déroulé et les permissions NTFS ont pu être conservées entre la source et la destination.

Mes premiers pas sur Azure, première partie

Comme vous le savez peut-être déjà, je suis assez captivé par le Cloud en général ; si j’ai touché à quelques produits de petits acteurs, j’ai également utilisé Azure il y a de cela quelques années – 2012 pour être plus précis. Six ans plus tard, je remets le couvert, avec l’intention de creuser plus profondément et de pouvoir comprendre la philosophie, le fonctionnement et les possibilités ouvertes par ce produit qui s’annonce impressionnant. Je vais donc publier mes notes et mon ressenti à ce sujet, en plusieurs fois, qui détailleront ma découverte du produit de Microsoft ; j’espère retransmettre ma curiosité et l’excitation ressentie à l’usage de cette solution et que cela vous donnera envie d’essayer.

Et essayer, rien de plus facile, possédant un crédit de 170 euros valable 30 jours sur simple inscription. Une inscription qui demande quelques informations personnelles, dont un numéro de téléphone valide ainsi qu’un numéro de carte bleue. Mais Microsoft assure qu’elle ne sera pas débitée sans abonnement Azure ou mon consentement après que le crédit d’essai soit épuisé ou expiré.

Premiers pas sur l’interface, c’est quelque peu déroutant.

Les objets sur la gauche, et le tableau de bord au milieu, faisant la part belle aux raccourcis et aux ressources. En fait, les ressources sont les différents objets qui constituent une infrastructure dans Azure. Par exemple, lorsque je vais créer une machine, elle sera découpée en plusieurs ressources : une machine virtuelle, un ou plusieurs disques, une (ou plusieurs) adresses IP, réseaux virtuels, etc.

Droit au but, je vais créer une ressource, une machine virtuelle toute simple, un Windows 10, pour me déployer un bureau. Je clique donc sur le lien de création, et je dois choisir ce que je souhaite installer.

En fait, Azure permet de monter presque tout. Si l’on cherche à développer une application web, il procèdera à la création d’une machine frontend web, une machine backend, un serveur SQL, etc.

C’est parti, je me lance, création d’une VM Windows 10.

La VM fait donc partie d’un pool de ressources (qui, comme je l’ai expliqué plus haut, n’a rien à voir avec un pool de ressources de calcul), et est déployable dans de nombreux datacenters. De nombreuses images sont déployables, dans le cadre de Windows 10 il est même possible de choisir son niveau de mise à jour. Le principal hic concernant la configuration de base est la taille de la VM. En effet, les dénominations sont quelque peu complexes, bien que la tarification estimée au mois soit affichée :
Il existe donc des offres « Standard » et « de base » (ça a quand même l’air relativement similaire), des familles de calcul optimal et d’autres d’usage général… des machines avec un stockage temporaire (je reviendrai dessus plus tard), etc. Il y a le choix pour vraiment obtenir une configuration comme on le désire, mais c’est quand même déroutant au départ. Au moins la tarification mensuelle est claire, même si au final elle est calculée à la minute de fonctionnement. Je pars pour une machine B1ms : 1 coeur virtuel et 2 Go de mémoire vive, le minimum.

La suite : les disques.

Par défaut, les machines Windows ont un disque système calibré à 128 Go, et en fonction de la taille de la machine commandée, un disque supplémentaire « temporaire ». Le disque temporaire est un disque qui est monté vierge à chaque extinction complète de la VM (lorsque les ressources sont libérées côté Azure, j’y reviendrai également). Il ne peut donc pas servir pour du stockage usuel, mais pourquoi pas pour des fichiers temporaires ou pour le fichier d’échange. Pas bête. Naturellement, il est possible d’ajouter des disques supplémentaires. La technologie des disques est laissée au choix de l’utilisateur, avec bien évidemment des performances supérieures pour les SSD, avec un prix en rapport.

La configuration de mise en réseau permet de définir comment la machine va être raccordée aux autres machines que l’on possède et éventuellement à internet.

L’adresse IP publique est par défaut dynamique, en sachant qu’un nom DNS fixe peut être défini (quelque peu long puisque pour une VM située en France, le sous-domaine est francecentral.cloudapp.azure.com). Il est cependant possible d’attribuer une adresse IP publique fixe.

Je dois avouer que j’ai pas encore trop saisi l’intérêt de la partie Administration, peut-être que ça viendra plus tard ; cela est sans doute utile dans le cadre de déploiement d’applications ou de maquettes de tests. Ayant monté cette VM Windows 10 pour me faire un bureau à distance avec des applications standards, je pense pas vraiment avoir besoin de cette feature.

La configuration de l’invité ne fonctionne pas pour les OS Windows car il s’agit d’un traitement CloudInit, cela concerne donc plus les machines fonctionnant sous GNU/Linux. Les étiquettes permettent de mettre des libellés sur les machines comme le nom l’indique bien. Pratique pour retrouver ses ressources dans de grands environnements.

Et enfin, au moment de la vérification de la configuration, juste avant de créer la ressource, on obtient la tarification définitive, dépendant de la taille de la machine, des disques, des options réseau ainsi que de l’emplacement où se situera la VM. En fonction des datacenters le prix peut varier du simple au double presque, ce qui n’est pas négligeable. A noter qu’en Europe de l’Ouest, l’Allemagne est le pays où la tarification horaire est la plus faible, bien que nous ne soyons pas si mal lotis en France.

Je valide la création. C’est en cours :

Puis quelques minutes plus tard, la création est terminée. Dans l’écran des ressources, je vois donc tout ce qui a été créé avec ma VM :

J’obtiens donc une vue toute éclatée de tous les éléments comportant la VM, que je peux ensuite parcourir pour obtenir plus de détails. La vue de la VM reste la plus complète avec une vision d’ensemble de celle-ci. Il est possible de s’y connecter en RDP avec des liens tout prêts, fonctionnant avec Windows mais aussi le client RDP disponible sous macOS.
Sur cette page, je peux démarrer ou arrêter la VM ; sachant qu’un arrêt sur cette page arrêtera la tarification de la VM. En effet, les ressources sont complètement désallouées, ce qui signifie qu’il faut un peu plus de temps qu’un simple boot pour démarrer la VM. Si cela peut être problématique pour des VM supposées démarrer quotidiennement et que l’on éteint pour des économies, dans un cadre personnel ou pour se monter un environnement de développement ou une maquette, cela n’a rien d’aberrant.
La ressource de l’IP publique permet de gérer la sécurité et le pare-feu.
A noter que lors de la création de VM, il est demandé si l’on souhaite par exemple autoriser le RDP entrant ou le SSH. Il est toujours possible d’accéder bien évidemment aux fonctionnalités de pare-feu depuis l’OS invité.
Parlons maintenant ce qui peut fâcher : la facturation. Pour le coup, tout est plutôt clair et limpide. En se rendant dans la partie facturation du portail Azure, les chiffres sont exposés et tous les coûts par ressources sont éclatés. Par exemple, ce « camembert » permet de comprendre le coût de chacun des éléments de la VM et d’identifier les coûts de multiples adresses IP pour une machine, ou encore de multiples disques.
Je vois donc qu’au bout de quelques jours, le stockage m’a coûté 1€, la ressource pour la VM (finalement 2 coeurs et 4 Go de RAM) 72 centimes et l’allocation d’IP sur le réseau 32 centimes. La partie droite sur le schéma permet de budgéter sur le mois les coûts. Plus de précision est évidemment possible, que ce soit par pool ou par type de ressource.
Cela me semble déjà pas mal pour un premier aperçu. Il existe pas mal d’autres pages par rapport à la VM, notamment tout l’aspect sauvegarde, supervision et automatisation. J’essayerai d’aborder cela prochainement dans un deuxième billet avec un peu plus de recul, en mettant en situation le fonctionnement d’une infrastructure de petite PME (2 contrôleurs de domaine – si possible en Windows Server 2019 si il est disponible d’ici là… – et quelques stations Windows 10) pour gérer la partie réseau ainsi que la séparation dans un autre pool.