Il y a des lundi matins où, arrivant tranquillement au travail et reposé du weekend, on est tellement heureux d’entamer une nouvelle semaine pleine de promesses de belles réalisations disruptives et innovantes, qui vont changer la donne. On fait quelques pas dans le bureau et là on voit toutes les têtes baissées, les regards fuyants… la tension est palpable. Pourtant, il n’y a pas eu de mise en production vendredi dernier et le monitoring indique que tout va bien.
Finalement, on découvre la raison de cette frayeur à la machine à café
Dans notre belle architecture Azure, quelqu’un a honteusement glissé une IP Publique, non prévue, n’apparaissant sur aucun schéma et dont l’usage reste un mystère.
Pas de panique, il existe un moyens de trouver son créateur, l’Activity Logs.
Il contient les informations sur les événements qui se sont produits dans la souscription Azure…On va donc pouvoir trouver le coupable. Connexion au portail, on va sur la bonne souscription ensuite sur l’IP publique en question et on affiche le journal d’activité.
Et la, rien. Aucune information. Le journal d’activité n’est conservé que 90 jours, la ressource étant plus ancienne, plus aucune information disponible.
Bilan de l’investigation : Elle date de plus de 3 mois. C’est un peu léger. Hélas, cette fois, on ne pourra pas faire grand chose de plus.
Comment remédier à cela?
Deux moyens pour se prémunir de ce genre de problème : Archiver l’Activity Logs dans un Storage Account, dont la persistance dans le temps ne sera pas limitée. Aussi, il est possible de mettre un Tag sur les ressources avec leur créateur.
Archivage de l’Activity Log
Il est possible d’exporter le journal d’activité vers un Storage Account ou un Event Hub (ou les deux).
La documentation pour le faire est ici : https://docs.microsoft.com/en-us/azure/azure-monitor/platform/activity-log-export.
A partir du moment où cet export est en place, il sera toujours possible de retrouver les informations sur le cycle de vie des ressources Azure.
Taguer les ressources
Pour aller un peu plus loin et avoir immédiatement l’information, on peut également poser un Tag sur les ressources créées avec son créateur (utilisateur ou Service Principal).
Il n’existe pas de mécanisme natif pour réaliser cette action, mais on peut le mettre un en place, en se basant principalement sur deux services Azure : Event Grid Subscription et Azure Function.
Event Grid Subscription sera utilisé pour récupérer les événements de création, de mise à jour ou de suppression de ressources. Les Azure Functions permettront d’effectuer un traitement suite à ces événements.
L’ensemble du code est disponible ici : https://github.com/chatoninthecloud/azure-tag-created-by
Fonctionnement
Création on modification d’une ressource
Voici le workflow lors de la création d’une nouvelle ressource où lors de sa mise à jour.
Lors de la création d’une ressource, un Event Grid Subscription va permettre de récupérer l’événement. On applique un filtre sur cette souscription, pour ne prendre en compte que les événements du type « Microsoft.Resources.ResourceWriteSuccess »
Il sera ensuite déposé dans une Azure Storage Queue pour être exploité de façon asynchrone.
Une Azure Function, avec un bidding sur cette queue, va effectuer le traitement contenant trois étapes :
- Vérifier que la ressource supporte les Tags (en utilisant ce référentiel https://github.com/tfitzmac/resource-capabilities/blob/master/tag-support.csv)
- Mettre à jour le référentiel des créateurs de ressources, stocké dans une Azure Storage Table
- Poser le tag « createdBy » si celui-ci n’est pas présent ou n’a pas la bonne valeur
Managed Service Identity est activée sur l’Azure Function, lui permettant d’avoir le rôle de Contributor sur la souscription et de pouvoir poser un Tag sur les ressources.
Suppression d’une ressource
Voici le workflow lors de la suppression d’une ressource.
Lors de la suppression d’une ressource, un Event Grid Subscription va permettre de récupérer l’événement. On applique un filtre sur cette souscription, pour ne prendre en compte que les événements du type « Microsoft.Resources.ResourceDeleteSuccess »
Il sera ensuite déposé dans une Azure Storage Queue pour être exploité de façon asynchrone.
Une Azure Function, avec un bidding sur cette queue, va effectuer le traitement qui ne contient qu’une seule étape :
- Supprimer la ligne dans la table référentiel associée à la ressource
Déploiement
Commencer par cloner le repository https://github.com/chatoninthecloud/azure-tag-created-by
Tools
Le déploiement nécessite les outils suivants :
- Terraform : https://www.terraform.io/downloads.html. Le template est fait pour s’exécuter avec la version 0.12
- Azure PowerShell module : https://docs.microsoft.com/bs-latn-ba/powershell/azure/install-az-ps?view=azps-2.5.0
- Azure Function tools : https://docs.microsoft.com/fr-fr/azure/azure-functions/functions-run-local
Service Principal
Créer un Service Principal pour Terraform. Il doit avoir le rôle de Owner sur la souscription car il a la responsabilité de :
- Créer toutes les ressources nécessaires (rôle Contributor obligatoire)
- Le Resource Group
- Le Storage Account
- La Function App, avec Managed Service Identity activé
- Un Application Insight pour le monitoring
- Les 2 Event Grid Subscription
- Affecter le rôle Contributor sur la souscription à l’identité managée de la Function App (rôle Owner obligatoire)
Il faudra créer un secret, que nous utiliserons plus tard dans la configuration de Terraform.
Storage Account
Créer un Storage Account et un container pour stocker les fichiers d’état de terraform (tfstate)
Terraform
Mettez à jour le fichier dev.tfvars.json à l’intérieur du dossier terraformenvironment
{ "region" : "region dans laquelle les ressources seront déployées", "resourceGroupName" : "Nom du groupe de ressources dans lequel les ressources seront créées", "storageAccountName" : "Nom du Storage Account", "resourceCreatedQueue" : "Nom de la queue utilisée comme endpoint pour l'Event Grid Subscription sur les ressources créées", "resourceDeletedQueue" : "Nom de la queue utilisée comme endpoint pour l'Event Grid Subscription sur les ressources supprimées", "tableName" : "Nom de la table utilisée comme référentiel des ressources créées", "resourceCreatedSubscription" : "Nom de l'Event Grid Subscription pour les ressources créées", "resourceDeletedSubscription" : "Nom de l'Event Grid Subscription pour les ressources supprimées", "appServicePlanName" : "Nom de l'App Service Plan", "functionAppName" : "Nom de la Function App", "applicationInsightName" : "Nom de l'Application Insight utilisée pour monitorer la Function App" }
Il est maintenant possible d’utiliser le format json pour gérer les variables dans Terraform. Je trouve personnellement ce format plus lisible.
Une valeur par défaut est présente pour toutes les ressources, n’ayant pas de contraintes d’unicité au niveau du nommage (c’est à dire le Storage Account et la Function App)
Mettez également à jour le fichier main.tf dans le dossier terraform
"terraform" : { "backend" : { "azurerm": { "storage_account_name" : "le nom du compte de stockage", "container_name" : "le nom du container", "key" : "le nom du fichier tfstate (dev.terraform.tfstate par exemple)" } } }
Il faut aussi définir les variables d’environnement suivantes dans votre shell pour configurer Terraform. Par exemple pour Windows
SET ARM_CLIENT_ID=L'applicationId du Service Principal SET ARM_CLIENT_SECRET=Un secret du Service Principal SET ARM_SUBSCRIPTION_ID=L'Id de la souscription SET ARM_TENANT_ID=L'Id du Tenant utilisé pour l'authentification SET ARM_ACCESS_KEY=Une des access key du compte de stockage pour les fichiers tfstate
Exécuter les commandes suivantes dans le dossier terraform
terraform init terraform plan -var-file=.environmentdev.tfvars.json
Si le plan d’exécution vous semble cohérent, vous pouvez déployer le template
terraform apply -var-file=.environmentdev.tfvars.json
Toutes les ressources Azure nécessaires sont maintenant déployées, on peut passer à l’Azure Function
Azure Function
La Function App va contenir 2 fonctions, resourceCreatedFunction, qui sera déclenchée lors de la création d’une ressource et resourceDeletedFunction, déclenchée lors de la suppression d’une ressource.
Le worker PowerShell est maintenant disponible dans les Azure Function V2, on va donc l’utiliser.
Au niveau du code, deux petites choses à noter.
- Le dossier Modules, à la racine du dossier function, va permettre d’importer automatiquement tous les modules se trouvant à l’intérieur au démarrage de l’Azure Function. Dans notre cas, le module AzTable est utilisé pour les opérations de manipulation de l’Azure Storage Table. Au sein de ce dossier, on peut enregistrer des modules provenant de la galerie PowerShell (https://www.powershellgallery.com/packages) ou alors ses propres modules
- le fichier profile.ps1, lui aussi exécuté au démarrage de l’Azure Function, permet de se connecter en utilisant l’identité managée. Le contexte d’exécution (local ou Azure) est déterminé en vérifiant l’existence de la variable d’environnement MSI_SECRET, définie automatiquement dans le cadre d’une exécution dans Azure. Cela permet d’exécuter ses fonctions localement dans l’émulateur ou alors dans le Cloud, sans avoir à toucher au code responsable de l’authentification (ce qui est bien pratique!).
Si vous avez modifié le nom par défaut des queues, il faudra modifié en conséquence les 2 fichiers function.json à l’intérieur de chaque fonction, en indiquant le nom de la queue servant comme endpoint, pour les messages de création dans le dossier resourceCreatedFunction, et celle servant de endpoint pour les messages de suppression dans le dossier resouceDeletedFunction
{ "bindings": [ { "name": "QueueItem", "type": "queueTrigger", "direction": "in", "queueName": Mettre le nom de la queue ici, "connection": "AzureWebJobsStorage" } ] }
On peut maintenant déployé ces 2 Azure functions en utilisant les commandes suivantes, dans le dossier function
Connect-AzAccount Get-AzSubscription -SubscriptionId "The subscription Id" | Select-AzSubscription func azure functionapp publish lenomdelafunctionapp
Tout est maintenant déployé.
Essayons de créer une nouvelle adresse IP publique
New-AzPublicIpAddress -Name "iamnothere" -ResourceGroupName azuretag-rg -Location northeurope -AllocationMethod Dynamic
Le traitement est asynchrone, et un petit délai est nécessaire pour que l’événement soit collecté. Après quelques (jusqu’à 2 minutes) secondes…Voici!
Le tag est bien posé.
Si on essaie de le supprimer, il sera créé de nouveau, et sa valeur sera prise depuis le référentiel.
Conclusion
On a maintenant en place un mécanisme permettant de poser un tag automatiquement sur les ressources le permettant, avec le nom du créateur.
C’est déjà un bon point. On pourra compléter cela en utilisant notamment :
- Des Azure Policies. Si les IPs Publiques n’étaient pas authorisées sur la souscription, on pourrait bloquer leur création à l’aide d’une Azure Policy.
- Une convention de nommage. Elle permet de pouvoir identifier rapidement le projet, l’équipe, la raison d’être d’une ressource
- Des Tags. On peut utiliser d’autres Tags (jusqu’à 50 par ressources), en ségréguant les ressources par environnement, produits, centres de coûts …