使用系统分配的托管标识访问 Azure Cosmos DB 数据

适用范围: NoSQL

在本文中,你将设置一个无论是否轮换密钥都能可靠运行的解决方案,以使用托管标识数据平面基于角色的访问控制来访问 Azure Cosmos DB 密钥。 本文中的示例使用 Azure Functions,但你可以使用任何支持托管标识的服务。

本文将介绍如何创建一个无需复制任何 Azure Cosmos DB 密钥即可访问 Azure Cosmos DB 数据的函数应用。 发出 HTTP 请求时,函数应用将会触发,然后列出所有现有数据库。

先决条件

  • 具有活动订阅的 Azure 帐户。 免费创建帐户

  • 现有的 Azure Cosmos DB API for NoSQL 帐户。 创建 Azure Cosmos DB API for NoSQL 帐户

  • 现有的Azure Functions 函数应用。 在 Azure 门户中创建你的第一个函数

  • Azure Functions Core Tools

  • 若要执行本文中的步骤,需安装 Azure CLI登录到 Azure

    先决条件检查

    1. 在终端或命令窗口中,将 Azure Functions 函数应用、Azure Cosmos DB 帐户和资源组的名称分别存储为名为 functionNamecosmosNameresourceGroupName 的 shell 变量。

      # Variable for function app name
      functionName="msdocs-function-app"
      
      # Variable for Azure Cosmos DB account name
      cosmosName="msdocs-cosmos-app"
      
      # Variable for resource group name
      resourceGroupName="msdocs-cosmos-functions-dotnet-identity"
      

      注意

      后续步骤中将会重用这些变量。 此示例假设 Azure Cosmos DB 帐户名称为 msdocs-cosmos-app,函数应用名称为 msdocs-function-app,资源组名称为 msdocs-cosmos-functions-dotnet-identity

    2. 使用 az functionapp show 命令查看函数应用的属性。

      az functionapp show \
          --resource-group $resourceGroupName \
          --name $functionName
      
    3. 使用 az webapp identity show 查看函数应用的系统分配托管标识的属性。

      az webapp identity show \
          --resource-group $resourceGroupName \
          --name $functionName
      
    4. 使用 az cosmosdb show 查看 Azure Cosmos DB 帐户的属性。

      az cosmosdb show \
          --resource-group $resourceGroupName \
          --name $cosmosName
      

创建 Azure Cosmos DB API for NoSQL 数据库

在此步骤中,你将创建两个数据库。

  1. 在终端或命令窗口中,使用 az cosmosdb sql database create 创建新的 products 数据库。

    az cosmosdb sql database create \
        --resource-group $resourceGroupName \
        --name products \
        --account-name $cosmosName
    
  2. 创建新的 customers 数据库。

    az cosmosdb sql database create \
        --resource-group $resourceGroupName \
        --name customers \
        --account-name $cosmosName
    

获取 Azure Cosmos DB API for NoSQL 终结点

在此步骤中,你将查询 API for NoSQL 帐户的文档终结点。

  1. 使用 az cosmosdb show 并将 query 参数设置为 documentEndpoint。 记录结果。 在稍后的步骤中会使用此值。

    az cosmosdb show \
        --resource-group $resourceGroupName \
        --name $cosmosName \
        --query documentEndpoint
    
    cosmosEndpoint=$(
        az cosmosdb show \
            --resource-group $resourceGroupName \
            --name $cosmosName \
            --query documentEndpoint \
            --output tsv
    )
    
    echo $cosmosEndpoint
    

    注意

    此变量将在后面的步骤中重用。

向 Azure Cosmos DB 帐户授予访问权限

在此步骤中,你将向函数应用的系统分配托管标识分配角色。 Azure Cosmos DB 有多个内置角色,可将这些角色分配到托管标识以进行控制平面访问。 对于数据平面访问,你将创建一个有权读取元数据的新自定义角色。

提示

若要详细了解最小特权访问权限的重要性,请参阅降低特权帐户的泄露风险一文。

  1. 使用 az cosmosdb show 并将 query 参数设置为 id。 将结果存储在名为 scope 的 shell 变量中。

    scope=$(
        az cosmosdb show \
            --resource-group $resourceGroupName \
            --name $cosmosName \
            --query id \
            --output tsv
    )
    
    echo $scope
    

    注意

    此变量将在后面的步骤中重用。

  2. 使用 az webapp identity show 并将 query 参数设置为 principalId。 将结果存储在名为 principal 的 shell 变量中。

    principal=$(
        az webapp identity show \
            --resource-group $resourceGroupName \
            --name $functionName \
            --query principalId \
            --output tsv
    )
    
    echo $principal
    
  3. 使用新自定义角色的配置创建新的 JSON 文件。

    {
        "RoleName": "Read Azure Cosmos DB Metadata",
        "Type": "CustomRole",
        "AssignableScopes": ["/"],
        "Permissions": [{
            "DataActions": [
                "Microsoft.DocumentDB/databaseAccounts/readMetadata"
            ]
        }]
    }
    

    提示

    可以使用 touch <filename> 或内置编辑器 (code .) 在 Azure Cli 中创建文件。

  4. 使用 az cosmosdb sql role definition create 通过自定义 JSON 对象创建名为 Read Azure Cosmos DB Metadata 的新角色定义。

    az cosmosdb sql role definition create \
        --resource-group $resourceGroupName \
        --account-name $cosmosName \
        --body @definition.json
    

    注意

    在此示例中,角色定义在名为 definition.json 的文件中定义。

  5. 使用 az role assignment createRead Azure Cosmos DB Metadata 角色分配到系统分配的托管标识。

    az cosmosdb sql role assignment create \
        --resource-group $resourceGroupName \
        --account-name $cosmosName \
        --role-definition-name "Read Azure Cosmos DB Metadata" \
        --principal-id $principal \
        --scope $scope
    

以编程方式访问 Azure Cosmos DB 密钥

现在,我们有了一个使用系统分配的托管标识的函数应用,该标识具有自定义角色。 以下函数应用将在 Azure Cosmos DB 帐户中查询数据库列表。

  1. 使用 --dotnet 参数在名为 csmsfunc 的文件夹中创建一个本地函数项目。 更改 shell 的目录

    func init csmsfunc --dotnet
    
    cd csmsfunc
    
  2. 创建一个新函数并将 template 参数设置为 httptrigger,将 name 设置为 readdatabases

    func new --template httptrigger --name readdatabases
    
  3. Azure.IdentityMicrosoft.Azure.Cosmos NuGet 包添加到 .NET 项目。 使用 dotnet build 生成项目。

    dotnet add package Azure.Identity
    
    dotnet add package Microsoft.Azure.Cosmos
    
    dotnet build
    
  4. 在集成开发环境 (IDE) 中打开函数代码。

    提示

    如果在本地使用 Azure CLI,可以打开 Visual Studio Code。

    code .
    
  5. 请将 readdatabases.cs 文件中的代码替换为此示例函数实现。 保存更新的文件。

    using System;
    using System.Collections.Generic;
    using System.Threading.Tasks;
    using Azure.Identity;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.Azure.Cosmos;
    using Microsoft.Azure.WebJobs;
    using Microsoft.Azure.WebJobs.Extensions.Http;
    using Microsoft.AspNetCore.Http;
    using Microsoft.Extensions.Logging;
    
    namespace csmsfunc
    {
        public static class readdatabases
        {
            [FunctionName("readdatabases")]
            public static async Task<IActionResult> Run(
                [HttpTrigger(AuthorizationLevel.Anonymous, "get")] HttpRequest req,
                ILogger log)
            {
                log.LogTrace("Start function");
    
                CosmosClient client = new CosmosClient(
                    accountEndpoint: Environment.GetEnvironmentVariable("COSMOS_ENDPOINT", EnvironmentVariableTarget.Process),
                    new DefaultAzureCredential()
                );
    
                using FeedIterator<DatabaseProperties> iterator = client.GetDatabaseQueryIterator<DatabaseProperties>();
    
                List<(string name, string uri)> databases = new();
                while(iterator.HasMoreResults)
                {
                    foreach(DatabaseProperties database in await iterator.ReadNextAsync())
                    {
                        log.LogTrace($"[Database Found]\t{database.Id}");
                        databases.Add((database.Id, database.SelfLink));
                    }
                }
    
                return new OkObjectResult(databases);
            }
        }
    }
    

(可选)在本地运行函数

在本地环境中,DefaultAzureCredential 类将使用各种本地凭据来确定当前标识。 虽然本操作指南不要求在本地运行,但你可以使用自己的标识或服务主体进行本地开发。

  1. 使用 az ad signed-in-user show 获取本地帐户的主体标识符。

    az ad signed-in-user show --query "id"
    
  2. 使用 az cosmosdb sql role assignment create 命令将本地帐户基于角色的访问控制访问权限分配给 Azure Cosmos DB 帐户。 使用 ID 为 00000000-0000-0000-0000-000000000002 的内置“Cosmos DB 数据参与者”角色。

    az cosmosdb sql role assignment create \
        --resource-group $resourceGroupName \
        --account-name $cosmosName \
        --role-definition-id "00000000-0000-0000-0000-000000000002" \
        --principal-id "<your-principal-id>" \
        --scope "/"
    
  3. 在 local.settings.json 文件中的 Values 对象内添加一个名为 COSMOS_ENDPOINT 的新设置。 该设置的值应是前面在本操作指南中记下的文档终结点。

    ...
    "Values": {
        ...
        "COSMOS_ENDPOINT": "https://msdocs-cosmos-app.documents.azure.com:443/",
        ...
    }
    ...
    

    注意

    为简洁起见,此 JSON 对象已缩短。 此 JSON 对象还包含一个示例值,该值假设帐户名称为 msdocs-cosmos-app

  4. 运行函数应用

    func start
    

部署到 Azure

发布后,DefaultAzureCredential 类将使用环境中的凭据或使用托管标识。 对于本指南,系统分配的托管标识将用作 CosmosClient 构造函数的凭据。

  1. 在已部署到 Azure 的函数应用中指定 COSMOS_ENDPOINT 设置。

    az functionapp config appsettings set \
        --resource-group $resourceGroupName \
        --name $functionName \
        --settings "COSMOS_ENDPOINT=$cosmosEndpoint"
    
  2. 通过重用 functionName shell 变量将函数应用部署到 Azure:

    func azure functionapp publish $functionName
    
  3. 在 Azure 门户中测试函数