Sécuriser un compte Azure Maps avec un jeton SAP

Cet article explique comment créer un compte Azure Maps avec un jeton SAP stocké de façon sécurisée que vous pouvez utiliser pour appeler l’API REST Azure Maps.

Prérequis

  • Un abonnement Azure. Si vous n’avez pas encore de compte Azure, inscrivez-vous pour créer un compte gratuit.

  • Autorisation du rôle Propriétaire sur l’abonnement approprié. Vous avez besoin des autorisations de Propriétaire pour :

    • Créer un coffre de clés dans Azure Key Vault.
    • Créez une identité managée affectée par l’utilisateur.
    • Attribuer un rôle à l’identité managée.
    • Créer un compte Azure Maps.
  • Azure CLI est installé pour déployer les ressources.

Exemple de scénario : stockage sécurisé par jeton SAP

Une information d’identification de jeton SAP accorde le niveau d’accès spécifié lors de sa création à quiconque le détient, jusqu’à ce que le jeton expire ou que l’accès soit révoqué. Les applications qui utilisent l’authentification par jeton SAP doivent stocker les clés de façon sécurisée.

Ce scénario décrit comment stocker de façon sécurisée un jeton SAP en tant que secret dans Key Vault et distribuer le jeton à un client public. Les événements de cycle de vie d’une application peuvent générer de nouveaux jetons SAP sans interrompre les connexions actives qui utilisent des jetons existants.

Pour plus d’informations sur la configuration de Key Vault, consultez le guide du développeur Azure Key Vault.

L’exemple de scénario suivant utilise deux déploiements de modèles Azure Resource Manager (ARM) pour effectuer les étapes suivantes :

  1. Création d’un coffre de clés
  2. Créez une identité managée affectée par l’utilisateur.
  3. Attribuez un rôle de lecteur de données Azure Maps de contrôle d’accès en fonction du rôle (RBAC) à l’identité managée affectée par l’utilisateur.
  4. Créez un compte Azure Maps avec une configuration CORS (Cross Origin Resource Sharing) et joignez l’identité managée affectée par l’utilisateur.
  5. Créez et enregistrez un jeton SAP dans Azure Key Vault.
  6. Récupérez le secret du jeton SAP à partir du coffre de clés.
  7. Créez une demande d’API REST Azure Maps qui utilise le jeton SAP.

Lorsque vous avez terminé, vous devriez voir les résultats de l’API REST Search Address (Non-Batch) Azure Maps sur PowerShell avec Azure CLI. Les ressources Azure sont déployées avec des autorisations pour se connecter au compte Azure Maps. Il existe des contrôles pour la limite maximale de débit, les régions autorisées, la stratégie CORS configurée localhost et Azure RBAC.

Déploiement de ressources Azure avec Azure CLI

Les étapes suivantes décrivent comment créer et configurer un compte Azure Maps avec l’authentification par jeton SAP. Dans cet exemple, Azure CLI s’exécute dans une instance PowerShell.

  1. Connectez-vous à votre abonnement Azure avec az login.

  2. Inscrivez des instances Key Vault, des identités managées et Azure Maps pour votre abonnement.

    az provider register --namespace Microsoft.KeyVault
    az provider register --namespace Microsoft.ManagedIdentity
    az provider register --namespace Microsoft.Maps
    
  3. Récupérez votre ID objet Microsoft Entra.

    $id = $(az rest --method GET --url 'https://graph.microsoft.com/v1.0/me?$select=id' --headers 'Content-Type=application/json' --query "id")
    
  4. Créez un fichier de modèle nommé prereq.azuredeploy.json avec le contenu suivant :

    {
        "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
        "contentVersion": "1.0.0.0",
        "parameters": {
            "location": {
                "type": "string",
                "defaultValue": "[resourceGroup().location]",
                "metadata": {
                    "description": "Specifies the location for all the resources."
                }
            },
            "keyVaultName": {
                "type": "string",
                "defaultValue": "[concat('vault', uniqueString(resourceGroup().id))]",
                "metadata": {
                    "description": "Specifies the name of the key vault."
                }
            },
            "userAssignedIdentityName": {
                "type": "string",
                "defaultValue": "[concat('identity', uniqueString(resourceGroup().id))]",
                "metadata": {
                    "description": "The name for your managed identity resource."
                }
            },
            "objectId": {
                "type": "string",
                "metadata": {
                    "description": "Specifies the object ID of a user, service principal, or security group in the Azure AD tenant for the vault. The object ID must be unique for the set of access policies. Get it by using Get-AzADUser or Get-AzADServicePrincipal cmdlets."
                }
            },
            "secretsPermissions": {
                "type": "array",
                "defaultValue": [
                    "list",
                    "get",
                    "set"
                ],
                "metadata": {
                    "description": "Specifies the permissions to secrets in the vault. Valid values are: all, get, list, set, delete, backup, restore, recover, and purge."
                }
            }
        },
        "resources": [
            {
                "type": "Microsoft.ManagedIdentity/userAssignedIdentities",
                "name": "[parameters('userAssignedIdentityName')]",
                "apiVersion": "2018-11-30",
                "location": "[parameters('location')]"
            },
            {
                "apiVersion": "2021-04-01-preview",
                "type": "Microsoft.KeyVault/vaults",
                "name": "[parameters('keyVaultName')]",
                "location": "[parameters('location')]",
                "properties": {
                    "tenantId": "[subscription().tenantId]",
                    "sku": {
                        "name": "Standard",
                        "family": "A"
                    },
                    "enabledForTemplateDeployment": true,
                    "accessPolicies": [
                        {
                            "objectId": "[parameters('objectId')]",
                            "tenantId": "[subscription().tenantId]",
                            "permissions": {
                                "secrets": "[parameters('secretsPermissions')]"
                            }
                        }
                    ]
                }
            }
        ],
        "outputs": {
            "userIdentityResourceId": {
                "type": "string",
                "value": "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('userAssignedIdentityName'))]"
            },
            "userAssignedIdentityPrincipalId": {
                "type": "string",
                "value": "[reference(parameters('userAssignedIdentityName')).principalId]"
            },
            "keyVaultName": {
                "type": "string",
                "value": "[parameters('keyVaultName')]"
            }
        }
    }
    
    
  5. Déployez les ressources préalables que vous avez créées à l’étape précédente. Fournissez votre propre valeur pour <group-name>. Veillez à utiliser le même location que le compte Azure Maps.

    az group create --name <group-name> --location "East US"
    $outputs = $(az deployment group create --name ExampleDeployment --resource-group <group-name> --template-file "./prereq.azuredeploy.json" --parameters objectId=$id --query "[properties.outputs.keyVaultName.value, properties.outputs.userAssignedIdentityPrincipalId.value, properties.outputs.userIdentityResourceId.value]" --output tsv)
    
  6. Créez un fichier de modèle azuredeploy.json pour provisionner le compte Azure Maps, l’attribution de rôle et le jeton SAP.

    Remarque

    Mise hors service du niveau tarifaire d’Azure Maps Gen1

    Le niveau tarifaire Gen1 est désormais déconseillé et sera mis hors service le 15/09/2026. Le niveau tarifaire Gen2 remplace le niveau tarifaire Gen1 (S0 et S1). Si le niveau tarifaire Gen1 est sélectionné pour votre compte Azure Maps, vous pouvez basculer vers la tarification Gen2 avant sa mise hors service. Sinon, il sera automatiquement mis à jour. Pour plus d’informations, consultez Gérer le niveau tarifaire de votre compte Azure Maps.

    {
        "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
        "contentVersion": "1.0.0.0",
        "parameters": {
            "location": {
                "type": "string",
                "defaultValue": "[resourceGroup().location]",
                "metadata": {
                    "description": "Specifies the location for all the resources."
                }
            },
            "keyVaultName": {
                "type": "string",
                "metadata": {
                    "description": "Specifies the resourceId of the key vault."
                }
            },
            "accountName": {
                "type": "string",
                "defaultValue": "[concat('map', uniqueString(resourceGroup().id))]",
                "metadata": {
                    "description": "The name for your Azure Maps account."
                }
            },
            "userAssignedIdentityResourceId": {
                "type": "string",
                "metadata": {
                    "description": "Specifies the resourceId for the user assigned managed identity resource."
                }
            },
            "userAssignedIdentityPrincipalId": {
                "type": "string",
                "metadata": {
                    "description": "Specifies the resourceId for the user assigned managed identity resource."
                }
            },
            "pricingTier": {
                "type": "string",
                "allowedValues": [
                    "S0",
                    "S1",
                    "G2"
                ],
                "defaultValue": "G2",
                "metadata": {
                    "description": "The pricing tier for the account. Use S0 for small-scale development. Use S1 or G2 for large-scale applications."
                }
            },
            "kind": {
                "type": "string",
                "allowedValues": [
                    "Gen1",
                    "Gen2"
                ],
                "defaultValue": "Gen2",
                "metadata": {
                    "description": "The pricing tier for the account. Use Gen1 for small-scale development. Use Gen2 for large-scale applications."
                }
            },
            "guid": {
                "type": "string",
                "defaultValue": "[guid(resourceGroup().id)]",
                "metadata": {
                    "description": "Input string for new GUID associated with assigning built in role types."
                }
            },
            "startDateTime": {
                "type": "string",
                "defaultValue": "[utcNow('u')]",
                "metadata": {
                    "description": "Current Universal DateTime in ISO 8601 'u' format to use as the start of the SAS token."
                }
            },
            "duration" : {
                "type": "string",
                "defaultValue": "P1Y",
                "metadata": {
                    "description": "The duration of the SAS token. P1Y is maximum, ISO 8601 format is expected."
                }
            },
            "maxRatePerSecond": {
                "type": "int",
                "defaultValue": 500,
                "minValue": 1,
                "maxValue": 500,
                "metadata": {
                    "description": "The approximate maximum rate per second the SAS token can be used."
                }
            },
            "signingKey": {
                "type": "string",
                "defaultValue": "primaryKey",
                "allowedValues": [
                    "primaryKey",
                    "seconaryKey"
                ],
                "metadata": {
                    "description": "The specified signing key which will be used to create the SAS token."
                }
            },
            "allowedOrigins": {
                "type": "array",
                "defaultValue": [],
                "maxLength": 10,
                "metadata": {
                    "description": "The specified application's web host header origins (example: https://www.azure.com) which the Azure Maps account allows for CORS."
                }
            }, 
            "allowedRegions": {
                "type": "array",
                "defaultValue": [],
                "metadata": {
                    "description": "The specified SAS token allowed locations where the token may be used."
                }
            }
        },
        "variables": {
            "accountId": "[resourceId('Microsoft.Maps/accounts', parameters('accountName'))]",
            "Azure Maps Data Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '423170ca-a8f6-4b0f-8487-9e4eb8f49bfa')]",
            "sasParameters": {
                "signingKey": "[parameters('signingKey')]",
                "principalId": "[parameters('userAssignedIdentityPrincipalId')]",
                "maxRatePerSecond": "[parameters('maxRatePerSecond')]",
                "start": "[parameters('startDateTime')]",
                "expiry": "[dateTimeAdd(parameters('startDateTime'), parameters('duration'))]",
                "regions": "[parameters('allowedRegions')]"
            }
        },
        "resources": [
            {
                "name": "[parameters('accountName')]",
                "type": "Microsoft.Maps/accounts",
                "apiVersion": "2023-06-01",
                "location": "[parameters('location')]",
                "sku": {
                    "name": "[parameters('pricingTier')]"
                },
                "kind": "[parameters('kind')]",
                "properties": {
                    "cors": {
                        "corsRules": [
                            {
                                "allowedOrigins": "[parameters('allowedOrigins')]"
                            }
                        ]
                    }
                },
                "identity": {
                    "type": "UserAssigned",
                    "userAssignedIdentities": {
                        "[parameters('userAssignedIdentityResourceId')]": {}
                    }
                }
            },
            {
                "apiVersion": "2020-04-01-preview",
                "name": "[concat(parameters('accountName'), '/Microsoft.Authorization/', parameters('guid'))]",
                "type": "Microsoft.Maps/accounts/providers/roleAssignments",
                "dependsOn": [
                    "[parameters('accountName')]"
                ],
                "properties": {
                    "roleDefinitionId": "[variables('Azure Maps Data Reader')]",
                    "principalId": "[parameters('userAssignedIdentityPrincipalId')]",
                    "principalType": "ServicePrincipal"
                }
            },
            {
                "apiVersion": "2021-04-01-preview",
                "type": "Microsoft.KeyVault/vaults/secrets",
                "name": "[concat(parameters('keyVaultName'), '/', parameters('accountName'))]",
                "dependsOn": [
                    "[variables('accountId')]"
                ],
                "tags": {
                    "signingKey": "[variables('sasParameters').signingKey]",
                    "start" : "[variables('sasParameters').start]",
                    "expiry" : "[variables('sasParameters').expiry]"
                },
                "properties": {
                    "value": "[listSas(variables('accountId'), '2023-06-01', variables('sasParameters')).accountSasToken]"
                }
            }
        ]
    }
    
  7. Déployez le modèle à l’aide des paramètres d’ID de Key Vault et des ressources d’identité managée créées à l’étape précédente. Fournissez votre propre valeur pour <group-name>. Lors de la création du jeton SAS, vous définissez le paramètre allowedRegions sur eastus, westus2 et westcentralus. Vous pouvez ensuite utiliser ces emplacements pour effectuer des requêtes HTTP sur le point de terminaison us.atlas.microsoft.com.

    Important

    Vous enregistrez le jeton SAP dans le coffre de clés pour empêcher ses informations d’identification d’apparaître dans les journaux de déploiement Azure. Le tags du secret de jeton SAP contient également le début, l’expiration et le nom de clé de signature pour mieux comprendre le moment où le jeton SAP expirera.

     az deployment group create --name ExampleDeployment --resource-group <group-name> --template-file "./azuredeploy.json" --parameters keyVaultName="$($outputs[0])" userAssignedIdentityPrincipalId="$($outputs[1])" userAssignedIdentityResourceId="$($outputs[2])" allowedOrigins="['http://localhost']" allowedRegions="['eastus', 'westus2', 'westcentralus']" maxRatePerSecond="10"
    
  8. Localisez et enregistrez une copie du secret de jeton SAP unique à partir de Key Vault.

    $secretId = $(az keyvault secret list --vault-name $outputs[0] --query "[? contains(name,'map')].id" --output tsv)
    $sasToken = $(az keyvault secret show --id "$secretId" --query "value" --output tsv)
    
  9. Testez le jeton SAP en effectuant une demande à un point de terminaison Azure Maps. Cet exemple spécifie la valeur us.atlas.microsoft.com pour garantir que votre demande est routée vers la zone géographique des États-Unis. Votre jeton SAP autorise les régions au sein de la zone géographique des États-Unis.

    az rest --method GET --url 'https://us.atlas.microsoft.com/search/address/json?api-version=1.0&query=1 Microsoft Way, Redmond, WA 98052' --headers "Authorization=jwt-sas $($sasToken)" --query "results[].address"
    

Exemple de script complet

Pour exécuter l’exemple complet, les fichiers de modèle suivants doivent se trouver dans le même répertoire que la session PowerShell active :

  • prereq.azuredeploy.json pour créer le coffre de clés et l’identité managée.
  • azuredeploy.json pour créer le compte Azure Maps, configurer l’attribution de rôle et l’identité managée, et stocker le jeton SAP dans le coffre de clés.
az login
az provider register --namespace Microsoft.KeyVault
az provider register --namespace Microsoft.ManagedIdentity
az provider register --namespace Microsoft.Maps

$id = $(az rest --method GET --url 'https://graph.microsoft.com/v1.0/me?$select=id' --headers 'Content-Type=application/json' --query "id")
az group create --name <group-name> --location "East US"
$outputs = $(az deployment group create --name ExampleDeployment --resource-group <group-name> --template-file "./prereq.azuredeploy.json" --parameters objectId=$id --query "[properties.outputs.keyVaultName.value, properties.outputs.userAssignedIdentityPrincipalId.value, properties.outputs.userIdentityResourceId.value]" --output tsv)
az deployment group create --name ExampleDeployment --resource-group <group-name> --template-file "./azuredeploy.json" --parameters keyVaultName="$($outputs[0])" userAssignedIdentityPrincipalId="$($outputs[1])" userAssignedIdentityResourceId="$($outputs[2])" allowedOrigins="['http://localhost']" allowedRegions="['eastus', 'westus2', 'westcentralus']" maxRatePerSecond="10"
$secretId = $(az keyvault secret list --vault-name $outputs[0] --query "[? contains(name,'map')].id" --output tsv)
$sasToken = $(az keyvault secret show --id "$secretId" --query "value" --output tsv)

az rest --method GET --url 'https://us.atlas.microsoft.com/search/address/json?api-version=1.0&query=1 Microsoft Way, Redmond, WA 98052' --headers "Authorization=jwt-sas $($sasToken)" --query "results[].address"

Exemple concret

Vous pouvez exécuter des requêtes à destination des API Azure Maps à partir de la plupart des clients, comme C#, Java ou JavaScript. Postman convertit une requête d’API en extrait de code client de base dans presque n’importe quel langage de programmation ou infrastructure que vous choisissez. Vous pouvez utiliser cet extrait de code généré dans vos applications frontales.

L’exemple de code JavaScript suivant montre comment utiliser votre jeton SAP avec l’API JavaScript Fetch pour obtenir et retourner des informations Azure Maps. L’exemple utilise l’API Obtenir l’adresse recherchée version 1.0. Fournissez votre propre valeur pour <your SAS token>.

Pour que cet exemple fonctionne, veillez à l’exécuter à partir de la même origine que allowedOrigins pour l’appel d’API. Par exemple, si vous fournissez https://contoso.com comme élément allowedOrigins dans l’appel d’API, la page HTML qui héberge le script JavaScript doit être https://contoso.com.

async function getData(url = 'https://us.atlas.microsoft.com/search/address/json?api-version=1.0&query=1 Microsoft Way, Redmond, WA 98052') {
  const response = await fetch(url, {
    method: 'GET',
    mode: 'cors',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': 'jwt-sas <your SAS token>',
    }
  });
  return response.json(); // parses JSON response into native JavaScript objects
}

postData('https://us.atlas.microsoft.com/search/address/json?api-version=1.0&query=1 Microsoft Way, Redmond, WA 98052')
  .then(data => {
    console.log(data); // JSON data parsed by `data.json()` call
  });

Nettoyer les ressources

Lorsque vous n’avez plus besoin des ressources Azure, vous pouvez les supprimer :

az group delete --name {group-name}

Étapes suivantes

Déployez un modèle ARM de démarrage rapide pour créer un compte Azure Maps qui utilise un jeton SAP :

Pour obtenir des exemples plus détaillés, consultez :

Recherchez les métriques d’utilisation de l’API pour votre compte Azure Maps :

Explorez des échantillons qui montrent comment intégrer Microsoft Entra ID à Azure Maps :