SEPT 21

Générer des clés SAS pour vos storage grâce à KeyVault

En entreprise, la sécurité c’est le nerf de la guerre. Il n’est pas envisagable de concevoir une application sans aucune notion de sécurité, que cette application soit hébergée on-premise ou dans le Cloud (Azure de préférence).

Quand vous utilisez des comptes de stockage Azure pour du Blob, des Tables ou des Queues, il est possible que vous souhaitiez ne pas avoir vos clés de stockage dans votre solution ou dans la configuration disponible sur vos serveurs pour des questions de Leakage.

Il est possible de mettre en place plusieurs solutions pour ne pas avoir de clés dans vos configurations :

  • Utiliser un SPN qui aura pour rôle de se connecter à Azure pour récupérer la clé de votre stockage et si besoin générer une SAS Key
  • Mettre en place un SPN qui aura des accès via l’AD sur votre compte de stockage (cela sera traité dans un prochain article)
  • Utiliser un SPN qui aura accès à un KeyVault contenant vos clés de storage
  • Utiliser le KeyVault pour générer des SAS Key.

Cette dernière solution est en preview dans Azure, nous allons voir comment la mettre en place.

La première étape coniste à avoir les droits nécessaires sur le KeyVault pour effectuer l’opération d’ajout, et à attribuer les bons droits aux personnes ou au SPN qui l’utiliseront. A ce jour, il n’est pas possible de faire cette opération via le portail, donc voici un exemple en Powershell :

Set-AzureRmKeyVaultAccessPolicy -VaultName $keyVaultName -ObjectId $userPrincipalId -PermissionsToStorage get,list,delete,set,update,regeneratekey,getsas,listsas,deletesas,setsas,recover,backup,restore,purge

La deuxième étape consiste à associer votre KeyVault à un compte de stockage. Il est possible de faire cela en PowerShell via la commande suivante :

$storage = Get-AzureRMStorageAccount -StorageAccountName $storageAccountName -ResourceGroupName $storageAccountResourgeGroup

Add-AzureKeyVaultManagedStorageAccount -VaultName $keyvaultName -AccountName $storageAccountName -AccountResourceId $storage.Id -ActiveKeyName key2 -DisableAutoRegenerateKey

A noter que sur cette dernière étape il est possible d’activer la régénération des clés de manière automatique sur vos storages.

La dernière étape consiste à construire une SAS Key qui vous servira de template pour la génération des suivantes. Ce template sera par la suite utilisé pour récupérer une nouvelle clé. Voici comment faire cela en powershell :

$sasTokenName = "fullaccess"
$storageKey = (Get-AzureRmStorageAccountKey -ResourceGroupName $storageAccountResourgeGroup -Name $storageAccountName).Value[0]

$context = New-AzureStorageContext -StorageAccountName $storageAccountName -StorageAccountKey $storageKey -Protocol Https
$start = [System.DateTime]::Now.AddMinutes(-15)
$end = [System.DateTime]::Now.AddMinutes(15)
$token = New-AzureStorageAccountSASToken -Service Blob,File,Table,Queue -ResourceType Service,Container,Object -Permission "racwdlup" -Protocol HttpsOnly -StartTime $start -ExpiryTime $end -Context $context

$validityPeriod = $validityPeriod = [System.Timespan]::FromMinutes(30)
Set-AzureKeyVaultManagedStorageSasDefinition -VaultName $keyVaultName -AccountName $storageAccountName -Name $sasTokenName -ValidityPeriod $validityPeriod -SasType 'account' -TemplateUri $token

Il est important d’utiliser un “sasTokenName” explicite et unique pour chacune de vos SAS Key, car c’est celui-là qui sera utilisé par la suite.

Maintenant pour récupérer ma clé SAS, il me suffit d’appeler le KeyVault avec le secret suivant : StorageAccountName-sasTokenName, comme on peut le voir ci-dessous en Powershell :

$sasKey = (Get-AzureKeyVaultSecret -VaultName $keyvaultName -Name ("wwodemospn-fullaccess")).SecretValueText

Avec cette méthode, fini les clés de Storage qui trainent dans les fichiers de config.


SEPT 10

Azure Batch & Windows Container

Récemment j’ai eu la problématique suivante : trouver un moyen de limiter la consommation mémoire d’une tâche dans Azure Batch. S’agissant à la base d’une tâche réalisée en .Net basée sur un framework pas récent, j’ai décidé de conserver au maximum l’applicatif sans pour autant écrire beaucoup de code pour mettre en place cette limitation.

J’ai donc opté pour la mise en place de container Docker pour Windows, puisque Docker permet de mettre cela en place simplement grâce à une option lorsqu’on lance notre container.

En terme de modification, je dois donc juste m’assurer que je puisse packager mon applicatif dans un container docker et que je puisse utiliser l’option sur Batch.

Afin de tester la fonctionnalité Batch + Container Windows, j’ai donc réalisé les tâches suivantes :

  • Création d’un Azure Container Registry
  • Création d’un compte Azure Batch

Ma registry va me servir à créer mon image Docker et à valider l’usage d’une registry privée, et mon compte Azure Batch va me servir pour mes tests.

Commençons par mon code applicatif que je vais utiliser, il doit respecter les critères suivants :

  • Utiliser de la mémoire
  • Tourner avec le Framework .Net (ici je vais prendre le 4.7.2)
  • Avoir une durée de vie assez longue afin de pouvoir observer les différentes métriques et retours lors d’une éxecution

J’ai pris le parti de créer un programme infini qui écrit dans un Table Storage en gardant toutes les entrées en mémoire, cela donne donc ce code :

public static class Program
{
    private const string ConnectionString = "CLE EN DUR, C'EST UN TEST DE HAUT VOL";
    public static void Main(string[] args) 
    {
      var csa = CloudStorageAccount.Parse(ConnectionString);
      var tableClient = csa.CreateCloudTableClient();
      
      var batchTable = tableClient.GetTableReference("batch");
      batchTable.CreateIfNotExists(); 
      batchTable.Execute(TableOperation.Insert(new DynamicTableEntity("batch", (DateTime.MaxValue.Ticks - DateTime.UtcNow.Ticks).ToString()))); 
    	
      List<FakeItem> items = new List<FakeItem>(); 
	
      for (int i = 0; i < int.MaxValue; i++)
      {
        items.Add(new FakeItem()
        {
          Val = i,
          Id = Guid.NewGuid()
        });


        if (i % 100 == 0)
        {
          System.Diagnostics.Process currentProcess = System.Diagnostics.Process.GetCurrentProcess();
          long memoryUsage = currentProcess.WorkingSet64;

          batchTable.Execute(TableOperation.Insert(
          new MyEntity()
          {
            PartitionKey = "batch",
            RowKey = (DateTime.MaxValue.Ticks - DateTime.UtcNow.Ticks).ToString(),
            MemoryValue = memoryUsage
          }));

        }
      }

    }

    public class MyEntity : TableEntity
    {
      public long MemoryValue { get; set;}
    }

    public class FakeItem
    {
      public Guid Id { get; set; }
      public double Val { get; set;}
    }
}

Il ne reste plus qu’à packager tout cela avec un DockerFile, qui est le suivant :

FROM microsoft/dotnet-framework:4.7.2-sdk-windowsservercore-ltsc2016 AS build
WORKDIR /app

COPY *.sln .
COPY sample-app/*.csproj ./sample-app/
RUN dotnet restore

COPY . .
WORKDIR /app/sample-app
RUN dotnet build

FROM build AS publish
WORKDIR /app/sample-app
RUN dotnet publish -c Release -o out


FROM microsoft/dotnet-framework:4.7.2-runtime-windowsservercore-ltsc2016 AS runtime
WORKDIR /app
COPY --from=publish /app/sample-app/out ./

Avec le code et ce dockerfile, je peux soit construire l’image en local et la push sur mon ACR, soit directement en faisant un ACR Build afin de gagner du temps vu que les images Docker pour Windows sont assez volumineuses. Voici la commande pour effectuer un build distant :

az acr build --registry <azureregistryname> --file .\DockerFile --image <appname> --os windows .

Le fonctionnement d’Azure Batch s’articule autour de 3 notions :

  • Pool : Ensemble de serveur qui seront utilisés. Un compte Azure Batch peut contenir plusieurs types de pool
  • Job : Elément logique qui permet de regrouper nos différentes tâches
  • Task : Exécution de nos tâches de calcul, ici il s’agira de l’exécution de notre container

Commençons donc par créer notre pool pour nos tests, il est possible de faire ceci via le portail Azure, ou via du code comme ci-dessous :

ImageReference imageReference = new ImageReference(
		publisher: "MicrosoftWindowsServer",
		offer: "WindowsServer",
		sku: "2016-Datacenter-with-Containers",
		version: "latest");

// Specify a container registry
ContainerRegistry containerRegistry = new ContainerRegistry(
		registryServer: $"{AcrName}.azurecr.io",
		userName: AcrName,
		password: AcrKey);

// Create container configuration, prefetching Docker images from the container registry
ContainerConfiguration containerConfig = new ContainerConfiguration();
	containerConfig.ContainerImageNames = new List<string> {
		// Prefetch images. Unusable node if error
    $"{AcrName}.azurecr.io/{ContainerName}:{ContainerVersion}"
	};
	containerConfig.ContainerRegistries = new[] { containerRegistry };

VirtualMachineConfiguration virtualMachineConfiguration = new VirtualMachineConfiguration(
		imageReference: imageReference,
		nodeAgentSkuId: "batch.node.windows amd64");
	virtualMachineConfiguration.ContainerConfiguration = containerConfig;

// Create pool
CloudPool pool = batchClient.PoolOperations.CreatePool(
  poolId: BatchPoolId,
  virtualMachineSize: "Standard_D2_V2",
  targetDedicatedComputeNodes: 1,
  virtualMachineConfiguration: virtualMachineConfiguration);

pool.UserAccounts = new List<UserAccount>() { new UserAccount("God", "pwdtopsecure", ElevationLevel.Admin) }; 

pool.Commit();

Par ailleurs, pour les tests je ne recommande pas de mettre en place le préchargement des images car en cas d’erreur, Azure Batch indique que votre pool est inutilisable, il faudra donc le recréer, ce qui peut vous faire perdre beaucoup de temps.

Maintenant il faut créer un job et y ajouter une tâche, pour cela encore en CSharp :

string cmdLine = @"C:\app\sample-app.exe";
ContainerRegistry containerRegistry = new ContainerRegistry(
  registryServer: $"{AcrName}.azurecr.io",
  userName: AcrName,
  password: AcrKey);

TaskContainerSettings cmdContainerSettings = new TaskContainerSettings(
  imageName: $"{AcrName}.azurecr.io/{ContainerName}:{ContainerVersion}",
  containerRunOptions: "--memory 64m",
  registry: containerRegistry
  );

CloudTask containerTask = new CloudTask(Guid.NewGuid().ToString("N"), cmdLine);

containerTask.ContainerSettings = cmdContainerSettings;
containerTask.UserIdentity = new UserIdentity(new AutoUserSpecification(elevationLevel: ElevationLevel.Admin, scope: AutoUserScope.Pool));

batchClient.JobOperations.AddTask(job.Id, containerTask);

Le point important à savoir ici c’est pour la ligne de commande qui est exécutée, par défault elle se lance dans le répertoire dédié à votre tâche, et l’application dans votre container se trouve le plus souvent sur le C:

Pour le reste, on peut voir que j’ai la main sur les différentes options pour lancer mon container. Ici je peux donc limiter la mémoire maximale pour mon application. En cas de dépassement de celle-ci, mon code me renvoit une exception de type OutOfMemoryException

Lors de vos tests vous pouvez vous connecter sur les noeuds Batch afin de voir l’utilisation CPU / RAM de vos containers via la commande :

docker stats

SEPT 01

Service Fabric - Service DNS

Il existe plusieurs modèles de programmation sur Service Fabric qui sont les suivants :

  • Guest Executable
  • Containers
  • Reliable Services

Du coup il est possible de mettre tout et n’importe quoi dans Service Fabric, mais comme dans toute architecture microservices, il existe différentes problématiques dont notamment la communication entre les services. Ceux qui ont déjà mis en place des Reliables Services, vous savez qu’il est possible d’appeler le Naming Service afin de trouver le service que l’on souhaite et communiquer avec lui. Maintenant si vous avez mis en place des Guest Exe ou des Containers, vous n’avez pas accès au naming service via le SDK, même s’il est toujours possible de le faire via les API REST de Service Fabric.

Heureusement pour vous aider, il y a les Addons Service Fabric que vous pouvez mettre en place lors de la création de votre cluster, ici il s’agit du DNSService

 "addonFeatures": [
        "DnsService"
    ],

Pour le mettre en place rien de plus simple, je vais prendre un exemple simple d’une application composée de deux containers :

  • Front : sfcontainerfrontsample - Une image Docker qui contient un applicatif en node.js + httpserver
  • Back : sfcontainerbacksample - Une image Docker qui contient un applicatif en python + Flask

Pour le bon fonctionnement de mon application, il faut que mon application Front communique avec mon application Back. Pour réaliser cela, dans mon fichier ApplicationManifest.xml, quand je mets en place mon Service Back je lui renseigne le DNS que je veux utiliser :

      <Service Name="sfcontainerbacksample" ServiceDnsName="pythonback">
         <StatelessService ServiceTypeName="sfcontainerbacksampleType" InstanceCount="-1">
            <SingletonPartition />
         </StatelessService>
      </Service>

Mon application Front peut donc maintenant appeler mon application Back comme le montre le code ci-dessous :

var http = require('http')
var dns = require('dns'); 

var server = http.createServer(function (request, response) {
    var nodeName = process.env.Fabric_NodeName; 
    var ipAddress = ''; 
    var port = 8080; 
    
    dns.resolve('pythonback', function (errors, ipAddresses){
        if (errors) {
            response.end(errors.message);
        }
        else  {
            ipAddress = ipAddresses[0];

            var options = {
                host: ipAddress,
                port: port
            }; 

            callback = function(res) {
                var str = 'Python backend is running on: ';
                
                res.on('data', function (chunk) {
                    str += chunk;
                }); 

                res.on('end', function() {
                    str += "  \nNodeJs frontend is running on: ";
                    str += nodeName;
                    response.end(str);
                });
            }

            var req = http.request(options, callback); 

            req.on("error", err => {
                response.end(err.message);
            });

            req.end();
        }

    });

    request.on('error', err => {
        response.end(err.message);
    })
});

Voilà un service qui nous simplifie bien la vie pour la mise en place de la communication entre micro services. Il reste tout de même à mettre en place la notion de retry sur plusieurs partitions, car ici ce n’est pas ce que j’ai mis en place. Vous verrez néanmoins à l’usage que le tableau ipAddresses n’est pas toujours trié de la même manière, donc vous n’aurez pas toujours la même instance de votre application Back qui répond.

Les sources sont ici si vous voulez faire un test chez vous, ou sur un party cluster : https://github.com/wilfriedwoivre/meetups/tree/master/20180705/04-ServiceFabric


AOUT 22

Linqpad - Nouvelles fonctionnalités

Suite à un tweet de Joe Albahari pour la nouvelle beta de Linqpad qui annonce entre autre la fonctionnalité Interactive Regex Utility, j’ai décidé de la tester, et j’ai vu qu’il y a quelques autres fonctionnalités bien cools qui sont dedans !

Si comme moi vous utilisez Linqpad quasiment quotidiennent, il est là pour vous aider dans les cas suivants :

  • Eviter la création de la consoleApp1243525
  • Création d’algorithme
  • Tests de fonctionnalités
  • Debug

Maintenant vous allez pouvoir vous en servir pour créer des utilitaires, un peu comme la fonctionnalité présentée dans le tweet.

Voici un exemple très simple :

void Main()
{
	DumpContainer results = new DumpContainer();
	TextArea input = new TextArea(onTextInput: sender => {
		results.Content = sender.Text;
	});
	input.Dump("Input Text"); 
	results.Dump("Outputs");
}

Si vous exécuter cela dans la dernière version beta de LinqPad (la 5.33.7), vous obtiendrez le résultat suivant :

image

Vous pourrez retrouver l’ensemble du patch note de Release sur ce site : https://www.linqpad.net/download.aspx#beta


AOUT 20

Powershell - Utilisez vos différents profils

En cette période de vacances, il est grand temps de faire le plein d’astuces. Pour ceux qui font du scripting pour interargir avec Azure ou autre, vous savez qu’il est très souvent utile d’avoir des scripts réutilisables, et si possible toujours à porteé de main.

Si je prends mon cas, j’ai par exemple souvent besoin de me connecter à Azure sur une souscription spécifique, de configurer mon proxy, ou de créer des Resources Groups temporaires, ou bien alors de faire le grand ménage dans mes images Docker locales.

Il est très aisé de mettre tous ces scripts dans un dossier bien particulier pour les utiliser, mais il y a bien mieux, powershell peut vous aider à faire cela, grâce à la notion de profil.

Pour cela, le plus simple est d’ouvrir une console powershell et exécuter le code suivant :

$profile
---
Output : D:\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1

Ce fichier vous permet de mettre en place des scripts que vous pourrez utiliser dans tous vos shells. Pour cela rien de plus simple, il vous suffit de créér ce fichier s’il n’existe pas via la commande suivante :

if ((Test-Path $profile) -eq $false) {
    New-Item $profile
}
code $profile

Après, vous pouvez écrire ce que vous souhaitez dans ce fichier afin de pouvoir le réutiliser partout sans le chercher, comme par exemple ces commandes pour Docker

 Function Remove-DockerContainers {
    docker stop $(docker ps -a -q)
    docker rm $(docker ps -a -q)    
 }

 Function Remove-DockerImages {
    docker rmi -f $(docker images -a -q)
 }

Ce fichier est par ailleurs pris en charge au lancement d’un terminal, donc pensez à fermer et rouvrir votre terminal après le changement de ce fichier.

De plus, il existe un fichier par type de terminal, par exemple sur mon poste j’en ai 3 actuellement :

 ls D:\Documents\WindowsPowerShell


    Directory: D:\Documents\WindowsPowerShell


Mode                LastWriteTime         Length Namee
----                -------------         ------ ----
d-----        08-Nov-16     00:51                Scripts
-a----        27-Jul-18     11:25            846 Microsoft.PowerShellISE_profile.ps1
-a----        25-Jul-18     11:35            846 Microsoft.PowerShell_profile.ps1
-a----        25-Jul-18     11:35            846 Microsoft.VSCode_profile.ps1

A l’usage vous verrez que c’est très pratique pour les scripts récurrents, mais attention, si vous utilisez les fonctions mises en place dans votre profil, elles ne seront pas disponibles ni sur un autre poste ni depuis une usine logicielle.