Protección de una cuenta de Azure Maps con un token de SAS

En este artículo se describe cómo crear una cuenta de Azure Maps con un token de SAS almacenado con seguridad que se puede usar para llamar a la API REST de Azure Maps.

Prerrequisitos

  • Suscripción a Azure. Si aún no tiene una cuenta de Azure, regístrese para obtener una cuenta gratuita.

  • Permiso de rol Propietario en la suscripción de Azure. Necesita los permisos de Propietario para:

    • Crear un almacén de claves en Azure Key Vault.
    • Cree una identidad administrada asignada por el usuario.
    • Asignar un rol a la identidad administrada.
    • Crear una cuenta de Azure Maps.
  • La CLI de Azure está instalada para implementar los recursos.

Escenario de ejemplo: almacenamiento seguro de un token de SAS

Una credencial de token de SAS concede el nivel de acceso especificado a cualquier persona que lo tenga, hasta que el token expire o se revoque el acceso. Las aplicaciones que usan la autenticación de token de SAS tienen que almacenar las claves de forma segura.

En este escenario se almacena de forma segura un token de SAS como secreto en Azure Key Vault y se distribuye el token en un cliente público. Los eventos en el ciclo de vida de una aplicación pueden generar nuevos tokens de SAS sin interrumpir las conexiones activas que usan tokens existentes.

Para más información sobre cómo configurar Key Vault, consulte la Guía del desarrollador de Azure Key Vault.

En el escenario de ejemplo siguiente se usan dos implementaciones de plantilla de Azure Resource Manager (ARM) para realizar los pasos siguientes:

  1. Cree un almacén de claves.
  2. Cree una identidad administrada asignada por el usuario.
  3. Asigne el control de acceso basado en rol (RBAC) Lector de datos de Azure Maps a la identidad administrada asignada por el usuario.
  4. Cree una cuenta de Azure Maps con una configuración de uso compartido de recursos entre orígenes (CORS) y adjunte la identidad administrada asignada por el usuario.
  5. Cree y guarde un token de SAS en el almacén de Azure Key Vault.
  6. Recupere el secreto del token de SAS del almacén de claves.
  7. Cree una solicitud a la API REST de Azure Maps que usa el token de SAS.

Cuando haya finalizado, debería ver los resultados de la API REST de Azure Maps Search Address (Non-Batch) en PowerShell con la CLI de Azure. Los recursos de Azure se implementan con permisos para conectarse a la cuenta de Azure Maps. Hay controles para el límite de velocidad máximo, las regiones permitidas, la directiva de CORS configurada con localhost y RBAC de Azure.

Implementación de recursos de Azure con la CLI de Azure

En los pasos siguientes se describe cómo crear y configurar una cuenta de Azure Maps con autenticación de token de SAS. En este ejemplo, la CLI de Azure se ejecuta en una instancia de PowerShell.

  1. Inicie sesión en la suscripción de Azure con az login.

  2. Registre de Key Vault, identidades administradas y Azure Maps para la suscripción.

    az provider register --namespace Microsoft.KeyVault
    az provider register --namespace Microsoft.ManagedIdentity
    az provider register --namespace Microsoft.Maps
    
  3. Recupere su identificador de objeto de 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. Cree un archivo de plantilla denominado prereq.azuredeploy.json con el siguiente contenido:

    {
        "$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. Implemente los recursos de requisitos previos que creó en el paso anterior. Proporcione su propio valor para <group-name>. Asegúrese de usar el mismo valor location que la cuenta de 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. Cree un archivo de plantilla azuredeploy.json para aprovisionar la cuenta de Azure Maps, la asignación de roles y el token de SAS.

    Nota:

    Retirada del plan de tarifa Gen1 de Azure Maps

    El plan de tarifa de Gen1 ya está en desuso y se retirará el 15/9/26. El plan de tarifa Gen2 reemplaza al plan de tarifa Gen1 (tanto S0 como S1). Si la cuenta de Azure Maps tiene seleccionado el plan de tarifa Gen1, puede cambiar a los precios de Gen2 antes de que se retire; de lo contrario, se actualizará automáticamente. Para obtener más información, consulte Administración del plan de tarifa de la cuenta de 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. Implemente la plantilla con los parámetros de id. de los recursos de Azure Key Vault y de identidad administrada creados en el paso anterior. Proporcione su propio valor para <group-name>. Al crear el token de SAS, se establece el parámetro allowedRegions en eastus, westus2 y westcentralus. Puede usar estas ubicaciones para realizar solicitudes HTTP al punto de conexión us.atlas.microsoft.com.

    Importante

    Guarde el token de SAS en el almacén de claves para evitar que sus credenciales aparezcan en los registros de implementación de Azure. El elemento tags del secreto del token de SAS también contiene el inicio, el vencimiento y el nombre de la clave de firma para mostrar cuándo expirará el token de SAS.

     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. Busque y guarde una copia del secreto del token de SAS único de Azure 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. Pruebe el token de SAS enviando una solicitud a un punto de conexión de Azure Maps. En este ejemplo se especifica us.atlas.microsoft.com para asegurarse de que la solicitud se enruta a la geografía de EE. UU. El token de SAS permite regiones dentro de la geografía de EE. UU.

    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"
    

Ejemplo de script completo

Para ejecutar el ejemplo completo, los siguientes archivos de plantilla tienen que estar en el mismo directorio que la sesión actual de PowerShell:

  • prereq.azuredeploy.json para crear el almacén de claves y la identidad administrada.
  • azuredeploy.json para crear la cuenta de Azure Maps, configurar la asignación de roles e identidad administrada y almacenar el token de SAS en el almacén de claves.
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"

Ejemplo real

Puede enviar solicitudes a las API de Azure Maps desde la mayoría de los clientes, como C#, Java o JavaScript. Postman convierte una solicitud a la API en un fragmento de código de cliente básico en casi cualquier lenguaje de programación o marco que elija. Puede usar este fragmento de código generado en las aplicaciones front-end.

En el siguiente pequeño ejemplo de código de JavaScript se muestra cómo puede usar el token de SAS con la API Fetch de JavaScript para obtener y devolver información de Azure Maps. En el ejemplo se usa la versión 1.0 de la API Obtener dirección de búsqueda. Proporcione su propio valor para <your SAS token>.

Para que este ejemplo funcione, asegúrese de ejecutarlo desde el mismo origen que allowedOrigins para la llamada a la API. Por ejemplo, si proporciona https://contoso.com como allowedOrigins en la llamada a la API, la página HTML que hospeda el script de JavaScript tiene que ser 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
  });

Limpieza de recursos

Cuando ya no necesite los recursos de Azure, puede eliminarlos:

az group delete --name {group-name}

Pasos siguientes

Implemente una plantilla de ARM de inicio rápido para crear una cuenta de Azure Maps que use un token de SAS:

Para obtener ejemplos detallados, consulte:

Descubra las métricas de uso de API de la cuenta de Azure Maps:

Explore ejemplos que muestren cómo integrar Microsoft Entra ID con Azure Maps: