练习 - 访问 Azure Key Vault 中存储的机密

已完成

你了解启用用于 Azure 资源的托管标识后可创建供应用进行身份验证的标识。 现在创建一个使用该标识访问保管库中机密的应用。

读取 ASP.NET Core 应用中的密钥

Azure Key Vault API 是 REST API,用于处理与密钥和保管库相关的管理和使用。 保管库中的每个机密都有一个唯一 URL。 可使用 HTTP GET 请求检索机密值。

.NET Core 的官方 Key Vault 客户端是 Azure.Security.KeyVault.Secrets NuGet 包中的 SecretClient 类。 但无需直接使用它。 借助 ASP.NET Core 的 AddAzureKeyVault 方法,可以在启动时将保管库中的所有机密都加载到 Configuration API 中。 通过此技术,可以使用与其余配置相同的 IConfiguration 接口按名称访问所有机密。 使用 AddAzureKeyVault 的应用需要拥有对保管库的 GetList 权限。

提示

无论使用何种框架或语言来生成应用,都应将它设计为启动应用时在本地缓存密钥值或将密钥值加载到内存中,除非有具体原因不这样做。 每次在需要时都直接从保管库中读取它们既缓慢又昂贵,进而造成不必要的浪费。

AddAzureKeyVault 只要求输入保管库名称,可从本地应用配置中获取该名称。 它还会自动处理托管标识身份验证。 在部署到已启用 Azure 资源的托管标识的 Azure 应用服务的应用中使用时,它会检测托管标识令牌服务并使用该服务进行身份验证。 它非常适合大多数方案并实现所有最佳做法。 你在本单元的练习中使用它。

读取 Node.js 应用中的密钥

Azure Key Vault API 是 REST API,用于处理与密钥和保管库相关的管理和使用。 保管库中的每个机密都有一个唯一 URL。 可使用 HTTP GET 请求检索机密值。

Node.js 应用的官方 Key Vault 客户端是 @azure/keyvault-secrets npm 包中的 SecretClient 类。 在配置或代码中包含机密名称的应用通常使用其 getSecret 方法,以加载给定名称的机密值。 getSecret 要求应用标识必须拥有对保管库的 Get 权限。 旨在从保管库中加载所有机密的应用也将使用 listPropertiesOfSecrets 方法,以加载机密列表并要求必须拥有 List 权限。

应用必须先获得用于进行保管库身份验证的凭据对象,然后才能创建 SecretClient 实例。 若要进行身份验证,请使用 @azure/identity npm 包提供的 DefaultAzureCredentialDefaultAzureCredential 适用于大多数应用程序最终在 Azure 云中运行的情况,因为 DefaultAzureCredential 会将通常用于在部署时进行身份验证的凭据与用于在开发环境中进行身份验证的凭据组合在一起。 DefaultAzureCredential 尝试按顺序使用以下机制进行身份验证:

  • 环境。 DefaultAzureCredential 通过使用环境变量读取指定的帐户信息,并使用它进行身份验证。
  • 托管标识。 如果将应用程序部署到了启用了托管标识的 Azure 主机,则 DefaultAzureCredential 将使用该帐户进行身份验证。
  • Visual Studio Code。 如果开发人员曾使用 Visual Studio Code Azure 帐户插件进行身份验证,DefaultAzureCredential 会使用该帐户进行身份验证。
  • Azure CLI。 如果开发人员曾使用 Azure CLI az login 命令对帐户进行身份验证,DefaultAzureCredential 将使用该帐户进行身份验证。

有关详细信息,请参阅文档

提示

无论使用何种框架或语言来生成应用,都应将它设计为启动应用时在本地缓存密钥值或将密钥值加载到内存中,除非有具体原因不这样做。 每次在需要时都直接从保管库中读取它们既缓慢又昂贵,进而造成不必要的浪费。

处理应用中的机密

将机密加载到应用中后,就需要应用安全地处理机密。 在此模块中生成的应用中,要将机密值写出到客户端响应,为了证明它已成功加载,可在 Web 浏览器中查看它。 向客户端返回机密值并不是你通常会执行的操作! 通常情况下,使用机密执行诸如初始化数据库或远程 API 的客户端库等操作。

重要

一定要仔细查看代码,确保应用绝不会将机密写入任何类型的输出,包括日志、存储和响应。

练习

为了从保管库中加载机密,你创建新的 ASP.NET Core Web API,并使用 AddAzureKeyVault

创建应用

  1. 若要创建新的 ASP.NET Core Web API 应用并在编辑器中将其打开,请在 Azure Cloud Shell 中运行以下命令。

    dotnet new webapi -o KeyVaultDemoApp
    cd KeyVaultDemoApp
    code .
    
  2. 编辑器加载完成后,添加包含 AddAzureKeyVault 的 NuGet 包,并还原应用的所有依赖项。 在 Azure Cloud Shell 中运行以下命令。

    dotnet add package Azure.Identity
    dotnet add package Azure.Extensions.AspNetCore.Configuration.Secrets
    dotnet restore
    

添加代码以加载和使用机密

为了演示 Key Vault 的良好用法,请修改你的应用,以便在启动时从保管库加载机密。 还会添加新控制器,它的终结点可以从保管库中获取 SecretPassword 机密。

  1. 对于应用启动,请输入以下命令以启动编辑器。

    code .
    
  2. 打开 Program.cs,删除内容并将它们替换为以下代码。

    using System;
    using Azure.Identity;
    using Microsoft.AspNetCore;
    using Microsoft.AspNetCore.Hosting;
    using Microsoft.Extensions.Configuration;
    using Microsoft.Extensions.Hosting;
    
    namespace KeyVaultDemoApp
    {
        public class Program
        {
            public static void Main(string[] args)
            {
                CreateHostBuilder(args).Build().Run();
            }
    
            public static IHostBuilder CreateHostBuilder(string[] args) =>
                Host.CreateDefaultBuilder(args)
                    .ConfigureWebHostDefaults(webBuilder =>
                    {
                        webBuilder.UseStartup<Startup>();
                    })
                    .ConfigureAppConfiguration((context, config) =>
                    {
                        // Build the current set of configuration to load values from
                        // JSON files and environment variables, including VaultName.
                        var builtConfig = config.Build();
    
                        // Use VaultName from the configuration to create the full vault URI.
                        var vaultName = builtConfig["VaultName"];
                        Uri vaultUri = new Uri($"https://{vaultName}.vault.azure.net/");
    
                        // Load all secrets from the vault into configuration. This will automatically
                        // authenticate to the vault using a managed identity. If a managed identity
                        // is not available, it will check if Visual Studio and/or the Azure CLI are
                        // installed locally and see if they are configured with credentials that can
                        // access the vault.
                        config.AddAzureKeyVault(vaultUri, new DefaultAzureCredential());
                    });
        }
    }
    

    重要

    完成编辑后,请务必保存文件。 可使用“...”菜单或快捷键(Windows 和 Linux 上为 Ctrl+S,macOS 上为 Cmd+S)保存文件。

    起始代码的唯一变化是添加了 ConfigureAppConfiguration。 此元素是从配置中加载保管库名称并使用它调用 AddAzureKeyVault 的位置。

  3. 对于控制器,在名为 SecretTestController.csControllers 文件夹中创建一个新文件,并粘贴以下代码。

    提示

    若要新建文件,请在 Cloud Shell 中运行 touch 命令。 在这种情况下,运行 touch Controllers/SecretTestController.cs 命令。 若要在编辑器“文件”窗格的右上角查找该文件,请选择“刷新”图标。

    using System;
    using Microsoft.AspNetCore.Http;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.Extensions.Configuration;
    
    namespace KeyVaultDemoApp.Controllers
    {
        [Route("api/[controller]")]
        public class SecretTestController : ControllerBase
        {
            private readonly IConfiguration _configuration;
    
            public SecretTestController(IConfiguration configuration)
            {
                _configuration = configuration;
            }
    
            [HttpGet]
            public IActionResult Get()
            {
                // Get the secret value from configuration. This can be done anywhere
                // we have access to IConfiguration. This does not call the Key Vault
                // API, because the secrets were loaded at startup.
                var secretName = "SecretPassword";
                var secretValue = _configuration[secretName];
    
                if (secretValue == null)
                {
                    return StatusCode(
                        StatusCodes.Status500InternalServerError,
                        $"Error: No secret named {secretName} was found...");
                }
                else {
                    return Content($"Secret value: {secretValue}" +
                        Environment.NewLine + Environment.NewLine +
                        "This is for testing only! Never output a secret " +
                        "to a response or anywhere else in a real app!");
                }
            }
        }
    }
    
  4. 在 Azure Cloud Shell 中运行 dotnet build,确保已编译所有内容。 应用已可运行。 现在可将其加入 Azure 了!

使用 Express.js 新建 Web API,并使用 @azure/keyvault-secrets@azure/identity 包从保管库加载机密。

创建应用

若要初始化新的 Node.js 应用、安装所需的包,并在编辑器中打开新文件,请在 Azure Cloud Shell 中运行以下命令。

mkdir KeyVaultDemoApp
cd KeyVaultDemoApp
npm init -y
npm install @azure/identity @azure/keyvault-secrets express
touch app.js
code app.js

添加代码以加载和使用机密

为体现良好的 Key Vault 用法,应用会在启动时从保管库加载机密。 若要体现已加载机密,则创建一个终结点来显示 SecretPassword 机密的值。

  1. 若要设置应用,请将以下代码粘贴到编辑器中。 此代码导入所需的包,设置端口和保管库 URI 配置,并新建用于保留机密名称和值的对象。

    // Importing dependencies
    const { DefaultAzureCredential } = require("@azure/identity");
    const { SecretClient } = require("@azure/keyvault-secrets");
    const app = require('express')();
    
    // Initialize port
    const port = process.env.PORT || 3000;
    
    // Create Vault URI from App Settings
    const vaultUri = `https://${process.env.VaultName}.vault.azure.net/`;
    
    // Map of key vault secret names to values
    let vaultSecretsMap = {};
    

    重要

    请务必一边编辑文件一边保存,尤其是在快结束时。 可使用“...”菜单或快捷键(Windows 和 Linux 上为 Ctrl+S,macOS 上为 Cmd+S)保存文件。

  2. 接下来,添加用于对保管库进行身份验证并加载机密的代码。 将此代码添加为两个单独的函数。 在之前添加的代码后面插入几个空行,粘贴下面的代码。

    const getKeyVaultSecrets = async () => {
      // Create a key vault secret client
      let secretClient = new SecretClient(vaultUri, new DefaultAzureCredential());
      try {
        // Iterate through each secret in the vault
        listPropertiesOfSecrets = secretClient.listPropertiesOfSecrets();
        while (true) {
          let { done, value } = await listPropertiesOfSecrets.next();
          if (done) {
            break;
          }
          // Only load enabled secrets - getSecret will return an error for disabled secrets
          if (value.enabled) {
            const secret = await secretClient.getSecret(value.name);
            vaultSecretsMap[value.name] = secret.value;
          }
        }
      } catch(err) {
        console.log(err.message)
      }
    }
    
  3. 若要测试是否已加载机密,请创建 Express 终结点。 粘贴此代码。

    app.get('/api/SecretTest', (req, res) => {
      let secretName = 'SecretPassword';
      let response;
      if (secretName in vaultSecretsMap) {
        response = `Secret value: ${vaultSecretsMap[secretName]}\n\nThis is for testing only! Never output a secret to a response or anywhere else in a real app!`;
      } else {
        response = `Error: No secret named ${secretName} was found...`
      }
      res.type('text');
      res.send(response);
    });
    
  4. 调用用于从保管库加载机密的函数,再启动应用。 若要完成应用,请粘贴最后一个代码片段。

    (async () =>  {
      await getKeyVaultSecrets();
      app.listen(port, () => {
        console.log(`Server running at http://localhost:${port}`);
      });
    })().catch(err => console.log(err));
    
  5. 编写完代码后,请务必保存文件。

应用已可运行。 现在可将其加入 Azure 了!