Использование Azure Key Vault для передачи защищенного значения параметра во время развертывания

Вместо указания защищенного значения (например, пароля) непосредственно в шаблоне или файле параметров, можно получить значение во время развертывания из хранилища ключей Azure. Для получения значения нужно указать в параметре ссылку на хранилище ключей и секрет. Это значение никогда не будет раскрыто, так как указывается только его идентификатор в хранилище ключей.

Важно!

В этой статье основное внимание уделяется передаче конфиденциального значения в качестве параметра шаблона. Когда секрет передается в качестве параметра, хранилище ключей и группа ресурсов, в которую выполняется развертывание, могут находиться в разных подписках.

В статье не рассматривается настройка свойства виртуальной машины для URL-адреса сертификата в хранилище ключей. Шаблон быстрого запуска такого сценария описан в статье Установка сертификата на виртуальной машине из хранилища ключей Azure.

Развертывание хранилищ Key Vault и секретов

Чтобы получить доступ к хранилищу ключей во время развертывания шаблона, задайте для enabledForTemplateDeployment хранилища ключей значение true.

Если у вас уже есть хранилище ключей, убедитесь, что в нем разрешено развертывание шаблонов.

az keyvault update  --name ExampleVault --enabled-for-template-deployment true

Чтобы создать новое хранилище ключей и добавить секрет, используйте следующее.

az group create --name ExampleGroup --location centralus
az keyvault create \
  --name ExampleVault \
  --resource-group ExampleGroup \
  --location centralus \
  --enabled-for-template-deployment true
az keyvault secret set --vault-name ExampleVault --name "ExamplePassword" --value "hVFkk965BuUv"

В качестве владельца хранилища ключей вы автоматически имеете возможность создания секретов. Если необходимо разрешить другим пользователям создавать секреты, используйте следующее.

az keyvault set-policy \
  --upn <user-principal-name> \
  --name ExampleVault \
  --secret-permissions set delete get list

Политики доступа не нужны, если пользователь развертывает шаблон, который получает секрет. Добавляйте пользователя в политики доступа, только если он должен работать непосредственно с секретами. Разрешения на развертывание определены в следующем разделе.

Дополнительные сведения о создании хранилищ ключей и добавлении секретных кодов представлены в следующих статьях:

Предоставление развертыванию доступа к секретам

Пользователь, развертывающий шаблон, должен иметь на Microsoft.KeyVault/vaults/deploy/action доступ к области группы ресурсов и хранилищу ключей. Проверяя этот доступ, Azure Resource Manager предотвращает доступ неутвержденного пользователя к секрету, передавая идентификатор ресурса для хранилища ключей. Можно предоставить доступ к развертыванию пользователям без предоставления доступа на запись в секреты.

Оно имеется у ролей Владелец и Участник. Если вы создали хранилище ключей, вы являетесь владельцем и имеете такое разрешение.

Для других пользователей предоставьте разрешение Microsoft.KeyVault/vaults/deploy/action. Ниже показано, как создать роль с минимальным разрешением и назначить ее пользователю.

  1. Создание JSON-файла определения пользовательской роли

    {
      "Name": "Key Vault resource manager template deployment operator",
      "IsCustom": true,
      "Description": "Lets you deploy a resource manager template with the access to the secrets in the Key Vault.",
      "Actions": [
        "Microsoft.KeyVault/vaults/deploy/action"
      ],
      "NotActions": [],
      "DataActions": [],
      "NotDataActions": [],
      "AssignableScopes": [
        "/subscriptions/00000000-0000-0000-0000-000000000000"
      ]
    }
    

    Замените "00000000-0000-0000-0000-000000000000" на идентификатор подписки.

  2. Создание новой роли с помощью JSON-файла:

    az role definition create --role-definition "<path-to-role-file>"
    az role assignment create \
      --role "Key Vault resource manager template deployment operator" \
      --scope /subscriptions/<Subscription-id>/resourceGroups/<resource-group-name> \
      --assignee <user-principal-name> \
      --resource-group ExampleGroup
    

    В этих примерах роль назначается пользователю на уровне группы ресурсов.

При использовании хранилища ключей с шаблоном для управляемого приложения необходимо предоставить доступ к субъекту-службе Поставщик ресурсов устройств. Дополнительные сведения см. в статье Доступ к секрету Key Vault при развертывании Управляемых приложений Azure.

Секреты ссылки со статическим идентификатором

Этот способ дает возможность ссылаться на хранилище ключей в файле параметров, а не в шаблоне. На следующем рисунке показано, как файл параметров ссылается на секрет и передает это значение в шаблон.

Схема, показывающая Resource Manager интеграции хранилища ключей со статическим идентификатором.

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

Следующий шаблон развертывает сервер SQL, который содержит пароль администратора. В качестве параметра пароля задается защищенная строка. Но шаблон не указывает, откуда берется это значение.

{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "sqlServerName": {
      "type": "string"
    },
    "location": {
      "type": "string",
      "defaultValue": "[resourceGroup().location]"
    },
    "adminLogin": {
      "type": "string"
    },
    "adminPassword": {
      "type": "securestring"
    }
  },
  "resources": [
    {
      "type": "Microsoft.Sql/servers",
      "apiVersion": "2021-11-01",
      "name": "[parameters('sqlServerName')]",
      "location": "[parameters('location')]",
      "properties": {
        "administratorLogin": "[parameters('adminLogin')]",
        "administratorLoginPassword": "[parameters('adminPassword')]",
        "version": "12.0"
      }
    }
  ]
}

Теперь создайте файл параметров для предыдущего шаблона. В файле параметров укажите параметр, который совпадает с именем параметра в шаблоне. Для значения параметра используйте ссылку на секрет из хранилища ключей. Для ссылки на секретный код необходимо передать идентификатор ресурса хранилища ключей и имя секрета.

В приведенном ниже файле параметров секрет хранилища ключей уже должен существовать. При этом для идентификатора ресурса используется статическое значение.

{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "adminLogin": {
      "value": "exampleadmin"
    },
    "adminPassword": {
      "reference": {
        "keyVault": {
          "id": "/subscriptions/<subscription-id>/resourceGroups/<rg-name>/providers/Microsoft.KeyVault/vaults/<vault-name>"
        },
        "secretName": "ExamplePassword"
      }
    },
    "sqlServerName": {
      "value": "<your-server-name>"
    }
  }
}

Если необходимо использовать версию секрета, отличную от текущей, включите свойство secretVersion.

"secretName": "ExamplePassword",
"secretVersion": "cd91b2b7e10e492ebb870a6ee0591b68"

Разверните шаблон и передайте файл параметров.

az group create --name SqlGroup --location westus2
az deployment group create \
  --resource-group SqlGroup \
  --template-uri <template-file-URI> \
  --parameters <parameter-file>

Ссылка на секреты с динамическим идентификатором

Выше мы рассмотрели способ передачи статического идентификатора ресурса для секрета хранилища ключей из параметра. В некоторых сценариях нужно использовать ссылку на секрет хранилища ключей, который изменяется в зависимости от текущего развертывания. Или можно просто передать значения параметра в шаблон вместо создания ссылочного параметра в файле параметров. Решение заключается в динамическом создании идентификатора ресурса для секрета хранилища ключей с помощью связанного шаблона.

Динамически создавать идентификатор ресурса в файле параметров нельзя, так как в файле параметров не разрешены выражения шаблонов.

В родительском шаблоне добавьте вложенный шаблон и передайте параметр, содержащий динамически созданный идентификатор ресурса. На следующем рисунке показано, как параметр в связанном шаблоне ссылается на секрет.

Схема, иллюстрирующая динамическое создание идентификатора для секрета хранилища ключей.

Следующий шаблон динамически создает идентификатор хранилища ключей и передает его в качестве параметра.

{
  "$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": "The location where the resources will be deployed."
        }
      },
      "vaultName": {
        "type": "string",
        "metadata": {
          "description": "The name of the keyvault that contains the secret."
        }
      },
      "secretName": {
        "type": "string",
        "metadata": {
          "description": "The name of the secret."
        }
      },
      "vaultResourceGroupName": {
        "type": "string",
        "metadata": {
          "description": "The name of the resource group that contains the keyvault."
        }
      },
      "vaultSubscription": {
        "type": "string",
        "defaultValue": "[subscription().subscriptionId]",
        "metadata": {
          "description": "The name of the subscription that contains the keyvault."
        }
      }
  },
  "resources": [
    {
      "type": "Microsoft.Resources/deployments",
      "apiVersion": "2020-10-01",
      "name": "dynamicSecret",
      "properties": {
        "mode": "Incremental",
        "expressionEvaluationOptions": {
          "scope": "inner"
        },
        "template": {
          "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
          "contentVersion": "1.0.0.0",
          "parameters": {
            "adminLogin": {
              "type": "string"
            },
            "adminPassword": {
              "type": "securestring"
            },
            "location": {
              "type": "string"
            }
          },
          "variables": {
            "sqlServerName": "[concat('sql-', uniqueString(resourceGroup().id, 'sql'))]"
          },
          "resources": [
            {
              "type": "Microsoft.Sql/servers",
              "apiVersion": "2021-11-01",
              "name": "[variables('sqlServerName')]",
              "location": "[parameters('location')]",
              "properties": {
                "administratorLogin": "[parameters('adminLogin')]",
                "administratorLoginPassword": "[parameters('adminPassword')]"
              }
            }
          ],
          "outputs": {
            "sqlFQDN": {
              "type": "string",
              "value": "[reference(variables('sqlServerName')).fullyQualifiedDomainName]"
            }
          }
        },
        "parameters": {
          "location": {
            "value": "[parameters('location')]"
          },
          "adminLogin": {
            "value": "ghuser"
          },
          "adminPassword": {
            "reference": {
              "keyVault": {
                "id": "[resourceId(parameters('vaultSubscription'), parameters('vaultResourceGroupName'), 'Microsoft.KeyVault/vaults', parameters('vaultName'))]"
              },
              "secretName": "[parameters('secretName')]"
            }
          }
        }
      }
    }
  ]
}

Дальнейшие действия