自动轮换使用一组身份验证凭据的资源的机密

向Azure服务进行身份验证的最佳方式是使用托管标识,但某些方案需要访问密钥、密码或其他机密。 定期轮换这些机密,以减少泄露凭据的影响。

本教程介绍如何为使用一组身份验证凭据的数据库和服务自动执行定期机密轮换。 有关不同资产类型中的自动轮换概述,请参阅了解 Azure 密钥保管库 中的自动轮换

具体而言,本教程使用由Azure 事件网格通知触发的函数轮换存储在Azure 密钥保管库中的SQL Server密码:

轮换解决方案示意图

  1. 在机密到期日期之前的三十天,密钥保管库将“即将过期”事件发布到事件网格。
  2. 事件网格会检查事件订阅,并使用 HTTP POST 调用已订阅该事件的函数应用终结点。
  3. 函数应用接收机密信息,生成新的随机密码,并在密钥保管库中使用新密码为机密创建新版本。
  4. 函数应用使用新密码更新SQL Server。

注意

步骤 3 和步骤 4 之间可能存在延迟。 在该窗口中,密钥保管库中的机密无法向SQL Server进行身份验证。 如果任何步骤失败,事件网格将重试两个小时。

先决条件

如果还没有密钥保管库和SQL Server实例,请使用以下部署链接:

Image 显示标记为“部署到Azure”的按钮

  1. “资源组”下,选择“ 新建”,并为组命名。 本教程使用 akvrotation
  2. SQL 管理员登录名下,输入 SQL 管理员登录名。
  3. 选择“审核 + 创建”。
  4. 选择“创建”。

创建资源组

现在有一个密钥保管库和一个SQL Server实例。 在Azure CLI中验证此设置:

az resource list -o table -g akvrotation

结果类似于以下输出:

Name                     ResourceGroup         Location    Type                               Status
-----------------------  --------------------  ----------  ---------------------------------  --------
akvrotation-kv           akvrotation      chinaeast2      Microsoft.KeyVault/vaults
akvrotation-sql          akvrotation      chinaeast2      Microsoft.Sql/servers
akvrotation-sql/master   akvrotation      chinaeast2      Microsoft.Sql/servers/databases
akvrotation-sql2         akvrotation      chinaeast2      Microsoft.Sql/servers
akvrotation-sql2/master  akvrotation      chinaeast2      Microsoft.Sql/servers/databases

创建和部署SQL Server密码轮换函数

重要

此模板要求 Key Vault、SQL Server 实例和函数应用位于同一资源组中。

接下来,创建具有系统分配的托管标识和其他必需组件的函数应用,并部署SQL Server密码轮换函数。

该函数应用需要以下组件:

  • 一个 Azure 应用服务 计划。
  • 包含具有 Event Grid 触发器和 HTTP 触发器的 SQL 密码轮换函数的函数应用。
  • 用于函数应用触发器管理的存储帐户。
  • 一种 Azure 角色分配,允许函数应用的标识访问密钥保管库中的机密信息。
  • 针对事件的事件网格订阅。
  1. 选择Azure模板部署链接:

    Image 显示标记为“部署到Azure”的按钮

  2. 在“资源组”列表中选择“akvrotation”。

  3. SQL Server名称中,输入要轮换其密码的SQL Server实例的名称。

  4. 密钥保管库名称中,输入密钥保管库名称。

  5. 函数应用名称中,输入函数应用名称。

  6. 机密名称中,输入存储密码的机密名称。

  7. 仓库 URL 中,输入该函数代码的 GitHub 位置:https://github.com/Azure-Samples/KeyVault-Rotation-SQLPassword-Csharp.git

  8. 选择“审核 + 创建”。

  9. 选择“创建”。

注意

此 ARM 模板使用应用服务源代码管理(Kudu)部署,以从 GitHub 拉取函数代码。 对于生产环境工作负载,我们建议改用基于包的部署方式,例如 func azure functionapp publish 或配合 WEBSITE_RUN_FROM_PACKAGE 使用的 zip deploy

选择“查看+创建”

完成上述步骤后,你有一个存储帐户、服务器场和一个函数应用。 在Azure CLI中验证此设置:

az resource list -o table -g akvrotation

结果类似于以下输出:

Name                     ResourceGroup         Location    Type                               Status
-----------------------  --------------------  ----------  ---------------------------------  --------
akvrotation-kv           akvrotation       chinaeast2      Microsoft.KeyVault/vaults
akvrotation-sql          akvrotation       chinaeast2      Microsoft.Sql/servers
akvrotation-sql/master   akvrotation       chinaeast2      Microsoft.Sql/servers/databases
cfogyydrufs5wazfunctions akvrotation       chinaeast2      Microsoft.Storage/storageAccounts
akvrotation-fnapp        akvrotation       chinaeast2      Microsoft.Web/serverFarms
akvrotation-fnapp        akvrotation       chinaeast2      Microsoft.Web/sites
akvrotation-fnapp        akvrotation       chinaeast2      Microsoft.insights/components

有关创建函数应用和使用托管标识访问密钥保管库的详细信息,请参阅从 Azure 门户创建函数应用如何使用应用服务和Azure Functions的托管标识,以及通过 Azure RBAC 提供对密钥保管库的访问权限

注意

示例函数面向Azure Functions的.NET进程内模型。 对于新开发,请使用 .NET 隔离工作器模型,这是未来将持续受支持的模型。

旋转函数

在上一步中部署的函数使用事件来触发机密轮换。 它更新密钥保管库和 SQL 数据库。

函数的触发事件

此函数读取事件数据并运行轮换逻辑:

public static class SimpleRotationEventHandler
{
   [FunctionName("AKVSQLRotation")]
   public static void Run([EventGridTrigger]EventGridEvent eventGridEvent, ILogger log)
   {
      log.LogInformation("C# Event trigger function processed a request.");
      var secretName = eventGridEvent.Subject;
      var secretVersion = Regex.Match(eventGridEvent.Data.ToString(), "Version\":\"([a-z0-9]*)").Groups[1].ToString();
      var keyVaultName = Regex.Match(eventGridEvent.Topic, ".vaults.(.*)").Groups[1].ToString();
      log.LogInformation($"Key Vault Name: {keyVaultName}");
      log.LogInformation($"Secret Name: {secretName}");
      log.LogInformation($"Secret Version: {secretVersion}");

      SecretRotator.RotateSecret(log, secretName, keyVaultName);
   }
}

机密轮换逻辑

此轮换方法从机密中读取数据库信息,创建机密的新版本,并使用新机密更新数据库:

    public class SecretRotator
    {
		private const string CredentialIdTag = "CredentialId";
		private const string ProviderAddressTag = "ProviderAddress";
		private const string ValidityPeriodDaysTag = "ValidityPeriodDays";

		public static void RotateSecret(ILogger log, string secretName, string keyVaultName)
        {
            //Retrieve Current Secret
            var kvUri = "https://" + keyVaultName + ".vault.azure.cn";
            var client = new SecretClient(new Uri(kvUri), new DefaultAzureCredential());
            KeyVaultSecret secret = client.GetSecret(secretName);
            log.LogInformation("Secret Info Retrieved");

            //Retrieve Secret Info
            var credentialId = secret.Properties.Tags.ContainsKey(CredentialIdTag) ? secret.Properties.Tags[CredentialIdTag] : "";
            var providerAddress = secret.Properties.Tags.ContainsKey(ProviderAddressTag) ? secret.Properties.Tags[ProviderAddressTag] : "";
            var validityPeriodDays = secret.Properties.Tags.ContainsKey(ValidityPeriodDaysTag) ? secret.Properties.Tags[ValidityPeriodDaysTag] : "";
            log.LogInformation($"Provider Address: {providerAddress}");
            log.LogInformation($"Credential Id: {credentialId}");

            //Check Service Provider connection
            CheckServiceConnection(secret);
            log.LogInformation("Service Connection Validated");

            //Create new password
            var randomPassword = CreateRandomPassword();
            log.LogInformation("New Password Generated");

            //Add secret version with new password to Key Vault
            CreateNewSecretVersion(client, secret, randomPassword);
            log.LogInformation("New Secret Version Generated");

            //Update Service Provider with new password
            UpdateServicePassword(secret, randomPassword);
            log.LogInformation("Password Changed");
            log.LogInformation($"Secret Rotated Successfully");
        }
}

可以在 GitHub 上找到完整的代码。

将机密添加到密钥保管库

为自己分配密钥保管库 机密负责人角色,以便能够在保管库中管理机密:

az role assignment create --role "Key Vault Secrets Officer" --assignee <email-address-of-user> --scope /subscriptions/<subscription-id>/resourceGroups/<resource-group>/providers/Microsoft.KeyVault/vaults/akvrotation-kv

创建包含SQL Server资源 ID、SQL Server登录名和机密有效期(以天为单位)的标记的机密。 设置机密名称、SQL 数据库的初始密码(Simple123 在本示例中),并将过期日期设置为明天,以便立即触发轮换。

tomorrowDate=$(date -u -d "+1 day" +"%Y-%m-%dT%H:%M:%SZ")
az keyvault secret set --name sqlPassword --vault-name akvrotation-kv --value "Simple123" --tags "CredentialId=sqlAdmin" "ProviderAddress=<sql-database-resource-id>" "ValidityPeriodDays=90" --expires $tomorrowDate

将到期时间设置得较短,会在 15 分钟内发布一个 SecretNearExpiry 事件,从而触发该函数轮换机密。

测试和验证

若要验证机密是否已轮换,请转到 密钥保管库>Secrets

截图显示如何访问 密钥保管库 > Secrets.

打开“sqlPassword”机密并查看原始版本和轮换后的版本:

转到“机密”

创建 Web 应用

若要验证 SQL 凭据,请创建一个 Web 应用。 Web 应用从密钥保管库检索机密,从机密中提取 SQL 数据库信息和凭据,并测试与SQL Server的连接。

该 Web 应用需要以下组件:

  • 具有系统分配的托管标识的 Web 应用。
  • 一种角色分配,允许 Web 应用的托管标识读取密钥保管库中的机密。
  1. 选择Azure模板部署链接:

    Image 显示标记为“部署到Azure”的按钮

  2. 选择“akvrotation”资源组。

  3. SQL Server 名称字段中,输入其密码已轮换的 SQL Server 名称。

  4. 密钥保管库名称中,输入密钥保管库名称。

  5. 机密名称中,输入存储密码的机密名称。

  6. 仓库 URL 中,输入 Web 应用代码的 GitHub 位置:https://github.com/Azure-Samples/KeyVault-Rotation-SQLPassword-Csharp-WebApp.git

  7. 选择“审核 + 创建”。

  8. 选择“创建”。

打开 Web 应用

转到部署的应用程序 URL:

https://akvrotation-app.azurewebsites.net/

当应用程序在浏览器中打开时,你会看到“生成的机密值”,并会看到“数据库已连接”的值为“true”。

了解详细信息