Azure API Management est un service Azure sécurisé, fiable et scalable permettant de publier, consommer et gérer des APIs. Cette plateforme permet de renforcer la sécurité et d'analyser l'usage des APIs.

Selon les projets, proposer un front-end pour ses ressources peut répondre à plusieurs besoins:

  1. Gestion de la sécurité directement depuis API Management afin de restreindre l'accès aux ressources à différentes applications ou utilisateurs.
  2. Transformer les accès aux ressources. Aggréger plusieurs ressources en une seule réponse GET par exemple.
  3. Gérer le cache des réponses afin d'alléger les requêtes au Storage.

 

Scénario

Dans cet article, nous allons voir ensemble l'implémentation d'une API permettant de télécharger des Blobs privés depuis Azure Storage. Nous n'aborderons pas la sécurisation de cet API puisque celle-ci pourrait faire l'objet d'un article à part entière.

 

Azure Storage REST API

Nous allons commencer par créer un Compte de Stockage ou Storage Account.

La sécurisation globale de ce compte de stockage est définie dans l'onglet Settings >> Access Keys.

Notez bien comment récupérer la clé Key car elle vous sera utile dans la configuration de votre API.

 

 

Dans de compte nous allons créer un Blob Container. Lors de la création du container, veillez à saisir Public access level  : Private (no anonymous access)

 

Dans ce container, nous allons ajouter un fichier qui sera celui que nous essaierons de télécharger à la fin de cet article.

Pour cela, dans le container, cliquez sur le bouton Upload et laissez vous guider.

 

API Management

Partons du postulat que vous avez déja créé votre ressource API Management puisque celle-ci prend environ 45 minutes à être provisionnée :S

Il vous faut désormais créer une API que l'on appelera "Storage". Lors de la création de votre API, renseignez https://{storageAccountName}.blob.core.windows.net dans la propriété Web Service URL.

Notez également la propriété API URL Suffix, surtout si vous la customisez, puisqu'elle fera partie de la route d'accès à vos ressources.

Ouvrez ensuite votre API, sélectionnez Add Operation et créez une opération avec comme URL GET + /{container}/{blob}.

 

Vous avez désormais une API qui répond aux requêtes entrantes et les redirige vers l'API Blobs d'Azure Storage mais à ce stade, si vous tentez de la consommer vous recevrez une erreur d'authentification.

 

Policy

Nous avons défini une sécurité pour notre container dans Azure Storage (Authentication Type SAS). Nous allons donc désormais indiquer à notre API comment utiliser cette SAS Key pour authentifier les requêtes faites aux ressources.

Sélectionnez votre API puis l'opération créée auparavant et, dans l'onglet Design, sélectionnez le bouton Policies pour passer en mode édition.

La partie inbound correspond aux traitements effectuées sur les requêtes entrantes et c'est ici que nous allons travailler.

Nous allons commencer par définir des variables :

<set-variable name="APIVersion" value="2012-02-12" />
<set-variable name="RequestPath" value="@(context.Request.Url.Path)" />
<set-variable name="UTCNow" value="@(DateTime.UtcNow.ToString("R"))" />

 

Nous allons ensuite ajouter un header date aux requêtes entrantes :

<set-header name="date" exists-action="override">
<value>@(context.Variables.GetValueOrDefault<string>("UTCNow"))</value>
</set-header>
 

Enfin nous ajouterons un header Authorization : 

<set-header name="Authorization" exists-action="override">
<value>@{
var account = "storageproxy";
var key = "QOQDHuUMSB9ztzLn49SUph3LpL2gIjVXpfkpOuy8855kkOdMvlvgz7mJ9S2F6zPgN6nCSPdiy0+AgHQFeetBmQ==";
var splitPath = context.Variables.GetValueOrDefault<string>("RequestPath").Split('/');
var container = splitPath.Reverse().Skip(1).First();
var file = splitPath.Last();
var dateToSign = context.Variables.GetValueOrDefault<string>("UTCNow");
var stringToSign = string.Format("GET\n\n\n{0}\n/{1}/{2}/{3}", dateToSign, account, container, file);
string signature;
var unicodeKey = Convert.FromBase64String(key);
using (var hmacSha256 = new HMACSHA256(unicodeKey))
{
var dataToHmac = Encoding.UTF8.GetBytes(stringToSign);
signature = Convert.ToBase64String(hmacSha256.ComputeHash(dataToHmac));
}
var authorizationHeader = string.Format(
"{0} {1}:{2}",
"SharedKey",
account,
signature);
return authorizationHeader;
}</value>
</set-header>
 

Vous remplacerez bien évidemment les variables account et key par le nom du StorageAccount et la clef SAS définis dans l'étape du Storage.

Les autres lignes du script consistent à récupérer la requêtes entrante, à convertir la SAS Key en signature et à ajouter cette SharedKey composée en tant que header Authorization.

La requête ainsi modifiée est ensuite transmise à l'API de Storage qui en reconnaît l'authentificatione et renvoie la ressource demandée.

 

Test du proxy

Une fois toutes ces étapes suivies, vous devriez vous retrouver avec une API consommable depuis n'importe quel client (navigateur, Postman, etc.) permettant de télécharger vos ressources depuis une url dynamique:

http://{apiManagementUrl}/{apiUrlSuffix}/{container}/{blob}

 

Conclusion

Nous avons vu ensemble comment proxyfier des ressources Azure Storage via une API dans Azure API Management.

Avec cette logique, vous proposez aux utilisateurs ou applications clientes un accès aux ressources agnostique de la plateforme de ressources sous-jacente. Vous pouvez alors désormais gérer votre logique de cache, de connexion aux storages (SPN, MSI ?), apporter de l'intelligence fonctionnelle ou même switcher vers un autre provider sans douleur pour les clients.

 

Aller plus loin

Plusieurs pistes d'évolution et d'amélioration pour votre proxy:

  • Ajouter une authentification type JWT sur vos API pour éviter que ce soit open-bar
  • Appliquer la Policy sur l'API au lieu de l'opération afin qu'elle s'applique sur d'autres opérations
  • Utiliser les NamedValues d'API Management pour stocker vos variables account, key, etc.
  • Utiliser un SPN ou MSI pour éviter de stocker votre SAS Key dans API Management

Azure API Management propose depuis quelques mois une fonctionnalité très attendue : la gestion des versions et révisions.

Les versions permettent de regrouper plusieurs versions d'une même API au sein d'un Version Set alors que les révisions permettent de tester et déployer serinement des changements d'API.

azure api version set

Nous allons nous concentrer dans cet article sur le versionning d'API en créant un Version Set et en y ajoutant une première version d'API.

Azure API REST

Nous allons utiliser l'API REST d'Azure pour créer notre Version Set et notre API. Plusieurs raisons à l'utilisation de l'API REST :

  • La création d'un Version Set n'est pas disponible depuis le portail mais depuis les API Rest d'Azure et les commandes Powershell
  • L'association d'une API ou plutôt d'une version d'API n'est pas encore disponible depuis Azure Powershell
  • Le clic-clic n'est pas vraiment conseillé dans un environnment gérant plusieurs environnement et où le scripting de déploiement est à privilégier)

Récupérer un token d'accès aux Api REST d'Azure

Pour récupérer le token d'accès à l'AP REST d'Azure je vous invite à consulter le dernier article (obtenir un access token pour Azure ARM REST API).

Créer ou mettre à jour un API Version Set

 Afin de créer un API Version Set il faut connaître comment fonctionne un Version Set.

Un Version Set définit un regroupement logique des différentes version d'une même API. Pour distinguer ces versions, vos requêtes entrantes devront posséder un élément discriminant. Il peut s'agir d'un header ou d'un paramêtre dans l'Url.

Ce type de discriminant correspond au schéma de versionnement ou versioning scheme et il sera le même pour toutes les versions de l'API.

Plusieurs choix de schéma de versionnement s'offrent à vous :

  • Segment (ex: mon-api/v1)
  • Query (ex: mon-api?version=v1)
  • Header

Si vous optez pour le schéma Query, il vous faudra choisir versioningScheme = "Query" et définir la valeur de versionQueryName ("version" dans l'exemple précédent). En revanche, si vous optez pour le header, il faudra choisir versioningScheme = "Header" et définir la valeur de HeaderName.

Quand le choix de règle de versionnement est défini, il ne reste qu'à utiliser l'Api Azure REST en veillant à utiliser le verbe PUT afin de pouvoir rejouer le même script à chaque déploiement :

    # Create the body of the PUT request to the REST API
    $createOrUpdateApiVersionSetBody = @{
        name = $apiName
        versioningScheme = "Query"
        versionQueryName = $queryName
        description = "version by api-version"
    }

    $apiVersionSet = Invoke-RestMethod `
        -Method Put `
        -Uri ("https://management.azure.com/subscriptions/" + $subscriptionId +
              "/resourceGroups/" + $resourceGroupName +
              "/providers/Microsoft.ApiManagement/service/" + $apimServiceName +
              "/api-version-sets/" + $ApiVersionSetId +
              "?api-version=" + $apimApiVersion) `
        -Headers @{ Authorization = ("Bearer " + $accessToken)
                    "Content-Type" = "application/json" } `
        -Body ($createOrUpdateApiVersionSetBody | ConvertTo-Json -Compress -Depth 3)

Créer ou mettre à jour une version d'API

Une fois que nous avons créé notre Version Set, nous sommes prêts à créer une nouvelle version d'API associée à ce Version Set.

Afin de créer notre nouvelle version, nous utiliserons à nouveau l'API REST Azure via le endpoint d'Api.

Si la création d'Api est déjà bien documentée, l'association d'une version à un Version Set ne l'est pas.

Pour effectuer cette association dès la création de l'API nous n'avons qu'à rajouter des propriétés au Body de notre requête.

Ces propriétés sont l'id du Version Set (apiVersionSetId) et la version de l'API (apiVersion) à fournir dans le header, la query ou le segment selon le schéma choisi.

$importApiBody = @{
        properties = @{
            type = "Http"
            protocols = , "https"
            path = $apiPath
            displayName = $apiDisplayName
            serviceUrl = $serviceUrl
            apiVersionSetId = $apiVersionSet.id
            apiVersion = "v1"
        }
    }

    $api = Invoke-RestMethod `
        -Method Put `
        -Uri ("https://management.azure.com/subscriptions/" + $subscriptionId + `
              "/resourceGroups/" + $resourceGroupName + `
              "/providers/Microsoft.ApiManagement/service/" + $apimServiceName + `
              "/apis/" + $apiId + `
              "?api-version=" + $apimApiVersion) `
        -Headers @{ Authorization = ("Bearer " + $accessToken)
                    "Content-Type" = "application/json" } `
        -Body ($importApiBody | ConvertTo-Json -Compress -Depth 3)

Conclusion

Nous avons vu dans cet article comment utiliser l'API REST d'Azure pour créer un Version Set , une version d'API et associer cette version au version set. L'utilisation de l'API REST pourra probablement être remplacée par les commandes Azure powershell d'ici peu mais en attendant nous avons déjà de quoi scripter et automatiser nos déploiement de version d'API.

Seule bémol, notre API ne contient pour l'instant aucune route. Dans un prochain article nous verrons donc comment automatiser l'import d'une version d'API directement dans un Version Set depuis une spécification Swagger.