Защита учетной записи Azure Maps с помощью маркера SAS

В этой статье описывается, как создать учетную запись Azure Maps с безопасно хранимым маркером SAS, который можно использовать для вызова REST API Azure Maps.

Необходимые компоненты

  • Подписка Azure. Если у вас нет учетной записи Azure, зарегистрируйтесь для получения бесплатной пробной версии.

  • Разрешение роли владельца для подписки Azure. Вам потребуются разрешения владельца для выполнения следующих действий:

    • создание хранилища ключей в службе Azure Key Vault;
    • Создайте управляемое удостоверение, назначаемое пользователем.
    • назначение роли управляемому удостоверению;
    • создание учетной записи Azure Maps.
  • Azure CLI установлен для развертывания ресурсов.

Пример сценария: безопасное хранилище маркеров SAS

Учетные данные маркера SAS предоставляют указанный уровень доступа любому пользователю, который им владеет, до истечения срока действия маркера или при отмене доступа. Приложения, которые используют проверку подлинности по маркерам SAS, должны безопасно хранить свои ключи.

Этот сценарий безопасно сохраняет маркер SAS в качестве секрета в Azure Key Vault и распространяет маркер в общедоступном клиенте. События жизненного цикла приложения могут создавать новые маркеры SAS без прерывания активных соединений, использующих существующие маркеры.

Дополнительные сведения о настройке Key Vault см. в статье Руководство для разработчиков Azure Key Vault.

В следующем примере сценария используются два развертывания шаблона Azure Resource Manager (ARM) для выполнения перечисленных ниже действий.

  1. Создайте хранилище ключей.
  2. Создайте управляемое удостоверение, назначаемое пользователем.
  3. Назначение роли Читателя данных Azure Maps для управления доступом на основе ролей Azure (RBAC) назначенному пользователем управляемому удостоверению.
  4. Создание учетной записи Azure Maps с конфигурацией предоставления общего доступа к ресурсам независимо от источника (CORS) и присоединение назначенного пользователем управляемого удостоверения.
  5. Создание и сохранение маркера SAS в хранилище ключей Azure.
  6. Получение секрета маркера SAS из хранилища ключей.
  7. Создание запроса Azure Maps REST API, использующего маркер SAS.

После завершения вы должны увидеть результаты Azure Maps Search Address (Non-Batch) REST API в PowerShell с Azure CLI. Ресурсы Azure развертываются с разрешениями на подключение к учетной записи Azure Maps. Существуют элементы управления для ограничения максимальной скорости, разрешенных регионов, localhost настроенной политики CORS и Azure RBAC.

Развертывание ресурсов Azure с помощью Azure CLI

В следующих шагах описаны создание и настройка учетной записи Azure Maps с проверкой подлинности с помощью маркера SAS. В этом примере Azure CLI выполняется в экземпляре PowerShell.

  1. Войдите в подписку Azure с помощью az login.

  2. Зарегистрируйте Key Vault, Управляемые удостоверения и Azure Maps для подписки.

    az provider register --namespace Microsoft.KeyVault
    az provider register --namespace Microsoft.ManagedIdentity
    az provider register --namespace Microsoft.Maps
    
  3. Получите идентификатор объекта 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. Создайте файл шаблона с именем prereq.azuredeploy.json со следующим содержимым:

    {
        "$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. Разверните необходимые ресурсы, созданные на предыдущем шаге. Укажите собственное значение для <group-name>. Обязательно используйте то же location, что и для учетной записи 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. Создайте файл шаблона azuredeploy.json для предоставления учетной записи Azure Maps, назначения роли и маркера SAS.

    Примечание.

    Выход на пенсию ценовой категории Azure Карты 1-го поколения

    Ценовая категория 1-го поколения устарела и будет прекращена на 9.15.26. Ценовая категория 2-го поколения заменяет ценовую категорию 1-го поколения (как S0, так и S1). Если учетная запись Azure Карты выбрана ценовая категория 1-го поколения, вы можете перейти на ценовую категорию 2-го поколения, прежде чем она будет прекращена, в противном случае она будет автоматически обновлена. Дополнительные сведения см. в статье "Управление ценовой категорией учетной записи Azure Карты".

    {
        "$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. Разверните шаблон с помощью параметров идентификатора из Key Vault и ресурсов управляемых удостоверений, созданных на предыдущем шаге. Укажите собственное значение для <group-name>. При создании маркера SAS необходимо задать для параметра allowedRegions значение eastus, westus2 и westcentralus. Затем эти расположения можно использовать для выполнения HTTP-запросов к конечной точке us.atlas.microsoft.com.

    Важно!

    Маркер SAS сохраняется в хранилище ключей, чтобы предотвратить появление его учетных данных в журналах развертывания Azure. Кроме того, tags секрета маркера SAS содержит имя ключа для начала и окончания срока действия, а также ключ подписи, что позволяет определить, когда истечет срок действия маркера 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. Найдите и сохраните копию одного секрета маркера SAS из 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. Протестируйте маркер SAS, выполнив запрос к конечной точке Azure Maps. В этом примере указывается us.atlas.microsoft.com для обеспечения маршрутизации запроса в географию США. Ваш маркер SAS позволяет использовать географические регионы в пределах США.

    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"
    

Полный пример скрипта

Чтобы выполнить полный пример, следующие файлы шаблонов должны находиться в том же каталоге, что и текущий сеанс PowerShell:

  • prereq.azuredeploy.json для создания хранилища ключей и управляемого удостоверения.
  • azuredeploy.json для создания учетной записи Azure Maps, настройки назначения ролей и управляемого удостоверения, а также хранения маркера SAS в хранилище ключей.
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"

Реальный пример

Вы можете выполнять запросы к API Azure Maps от большинства клиентов, таких как C#, Java или JavaScript. Postman преобразует запрос API в базовый фрагмент кода клиента практически на любом языке программирования или любой выбранной платформе. Этот созданный фрагмент кода можно использовать в интерфейсных приложениях.

В следующем небольшом примере кода JavaScript показано, как можно использовать маркер SAS с Fetch API JavaScript для получения и возврата сведений Azure Maps. В примере используется API получения адресов поиска версии 1.0. Укажите собственное значение для <your SAS token>.

Чтобы этот пример работал, обязательно запустите его в том же источнике, что и allowedOrigins для вызова API. Например, если вы указываете https://contoso.com в качестве allowedOrigins в вызове API, HTML-страница, на которой размещается скрипт JavaScript, должна иметь вид 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
  });

Очистка ресурсов

Если созданные ресурсы Azure вам больше не нужны, удалите их.

az group delete --name {group-name}

Следующие шаги

Разверните шаблон быстрого запуска ARM, чтобы создать учетную запись Azure Maps, использующую маркер SAS:

Более подробные примеры см. в следующей статье.

Узнайте о метриках использования API для учетной записи Azure Maps:

Ознакомьтесь с примерами, в которые показано, как интегрировать идентификатор Microsoft Entra с Azure Карты: