使用密钥轮换和审核设置 Azure Key VaultSet up Azure Key Vault with key rotation and auditing

简介Introduction

有了密钥保管库以后,即可用它来存储密钥和机密。After you have a key vault, you can start using it to store keys and secrets. 应用程序不再需要保存密钥或机密,但可以根据需要从保管库请求它们。Your applications no longer need to persist your keys or secrets, but can request them from the vault as needed. 使用 Key Vault 可以更新密钥和机密,而不会影响应用程序,同时可以各种可能的方法管理密钥和机密。A key vault allows you to update keys and secrets without affecting the behavior of your application, which opens up a breadth of possibilities for your key and secret management.

重要

本文中的示例仅用于说明目的,The examples in this article are provided for illustration purposes only. 不应在生产环境中使用。They're not intended for production use.

本文逐步讲解:This article walks through:

  • 使用 Azure Key Vault 存储机密的示例。An example of using Azure Key Vault to store a secret. 在本文中,存储的机密是应用程序访问的 Azure 存储帐户密钥。In this article, the secret stored is the Azure storage account key accessed by an application.
  • 如何实现该存储帐户机密的计划轮换。How to implement a scheduled rotation of that storage account key.
  • 如何监视 Key Vault 审核日志,并在收到意外的请求时发出警报。How to monitor the key vault audit logs and raise alerts when unexpected requests are made.

备注

本文不详细说明 Key Vault 的初始设置。This article doesn't explain in detail the initial setup of your key vault. 有关信息,请参阅什么是 Azure 密钥保管库?For this information, see What is Azure Key Vault?. 有关跨平台命令行接口的说明,请参阅使用 Azure CLI 管理 Key VaultFor cross-platform command-line interface instructions, see Manage Key Vault using the Azure CLI.

备注

本文进行了更新,以便使用新的 Azure PowerShell Az 模块。This article has been updated to use the new Azure PowerShell Az module. 你仍然可以使用 AzureRM 模块,至少在 2020 年 12 月之前,它将继续接收 bug 修补程序。You can still use the AzureRM module, which will continue to receive bug fixes until at least December 2020. 若要详细了解新的 Az 模块和 AzureRM 兼容性,请参阅新 Azure Powershell Az 模块简介To learn more about the new Az module and AzureRM compatibility, see Introducing the new Azure PowerShell Az module. 有关 Az 模块安装说明,请参阅安装 Azure PowerShellFor Az module installation instructions, see Install Azure PowerShell.

设置密钥保管库Set up Key Vault

要使应用程序能够从 Key Vault 检索机密,必须先创建机密并将其上传到保管库。To enable an application to retrieve a secret from Key Vault, you must first create the secret and upload it to your vault.

启动 Azure PowerShell 会话,并使用以下命令登录用户的 Azure 帐户:Start an Azure PowerShell session and sign in to your Azure account with the following command:

Connect-AzAccount -Environment AzureChinaCloud

在弹出的浏览器窗口中,输入 Azure 帐户的用户名和密码。In the pop-up browser window, enter the username and password for your Azure account. PowerShell 会获取与此帐户关联的所有订阅。PowerShell will get all the subscriptions that are associated with this account. PowerShell 默认使用第一个订阅。PowerShell uses the first one by default.

如果有多个订阅,可能需要指定创建 Key Vault 时所用的订阅。If you have multiple subscriptions, you might have to specify the one that was used to create your key vault. 输入以下命令查看帐户的订阅:Enter the following to see the subscriptions for your account:

Get-AzSubscription

若要指定与要记录的 Key Vault 关联的订阅,请输入:To specify the subscription that's associated with the key vault you'll be logging, enter:

Set-AzContext -SubscriptionId <subscriptionID>

因为本文介绍了如何将存储帐户密钥存储为机密,因此,必须获取该存储帐户密钥。Because this article demonstrates storing a storage account key as a secret, you must get that storage account key.

Get-AzStorageAccountKey -ResourceGroupName <resourceGroupName> -Name <storageAccountName>

检索用户的机密(在本例中,为存储帐户密钥)后,必须将该密钥转换为安全字符串,并在 Key Vault 中使用该值创建机密。After retrieving your secret (in this case, your storage account key), you must convert that key to a secure string, and then create a secret with that value in your key vault.

$secretvalue = ConvertTo-SecureString <storageAccountKey> -AsPlainText -Force

Set-AzKeyVaultSecret -VaultName <vaultName> -Name <secretName> -SecretValue $secretvalue

接下来,获取你创建的机密的 URI。Next, get the URI for the secret you created. 在稍后的步骤中调用 Key Vault 和检索机密时,需要用到此 URI。You'll need this URI in a later step to call the key vault and retrieve your secret. 运行以下 PowerShell 命令,并记下 ID 值(即机密 URI):Run the following PowerShell command and make note of the ID value, which is the secret's URI:

Get-AzKeyVaultSecret –VaultName <vaultName>

设置应用程序Set up the application

存储机密后,可以在执行几个附加步骤后,使用代码检索并使用该机密。Now that you have a secret stored, you can use code to retrieve and use it after performing a few more steps.

首先必须将应用程序注册到 Azure Active Directory。First, you must register your application with Azure Active Directory. 然后向 Key Vault 告知应用程序信息,使其允许来自应用程序的请求。Then tell Key Vault your application information so that it can allow requests from your application.

备注

必须在与 Key Vault 相同的 Azure Active Directory 租户上创建应用程序。Your application must be created on the same Azure Active Directory tenant as your key vault.

  1. 打开“Azure Active Directory”。 Open Azure Active Directory.

  2. 选择“应用注册” 。Select App registrations.

  3. 选择“新建应用程序注册”,以将一个应用程序添加到 Azure Active Directory。 Select New application registration to add an application to Azure Active Directory.

    在 Azure Active Directory 中打开应用程序

  4. 在“创建”下,将应用程序类型保留为“Web 应用/API”,并为应用程序命名。 Under Create, leave the application type as Web app / API and give your application a name. 为应用程序指定“登录 URL” 。Give your application a Sign-on URL. 此 URL 可以是任意 URL,适合本演示即可。This URL can be anything you want for this demo.

    创建应用程序注册

  5. 将应用程序添加到 Azure Active Directory 后,应用程序页将会打开。After the application is added to Azure Active Directory, the application page opens. 依次选择“设置”、“属性”。 Select Settings, and then select Properties. 复制“应用程序 ID”值。 Copy the Application ID value. 后面的步骤需要用到。You'll need it in later steps.

接下来,为应用程序生成密钥,使其可与 Azure Active Directory 交互。Next, generate a key for your application so it can interact with Azure Active Directory. 若要创建密钥,请在“设置”下选择“密钥”。 To create a key, select Keys under Settings. 记下为 Azure Active Directory 应用程序生成的新密钥。Make note of the newly generated key for your Azure Active Directory application. 后面的步骤需要用到。You'll need it in a later step. 从此部分导航出来以后,该密钥将不可用。The key won't be available after you leave this section.

Azure Active Directory 应用密钥

在建立从应用程序到 Key Vault 的任何调用之前,必须让 Key Vault 知道应用程序及其权限。Before you establish any calls from your application into the key vault, you must tell the key vault about your application and its permissions. 以下命令使用 Azure Active Directory 应用中的保管库名称和应用程序 ID 为应用程序授予对 Key Vault 的 Get 访问权限。The following command uses the vault name and the application ID from your Azure Active Directory app to grant the application Get access to your key vault.

Set-AzKeyVaultAccessPolicy -VaultName <vaultName> -ServicePrincipalName <clientIDfromAzureAD> -PermissionsToSecrets Get

现在可以开始生成应用程序调用。You're now ready to start building your application calls. 在应用程序中,必须安装所需的 NuGet 包,以便与 Azure Key Vault 和 Azure Active Directory 交互。In your application, you must install the NuGet packages that are required to interact with Azure Key Vault and Azure Active Directory. 从 Visual Studio 包管理器控制台输入以下命令。From the Visual Studio Package Manager console, enter the following commands. 在编写本文时,Azure Active Directory 包的最新版本为 3.10.305231913,请确认最新版本并视需要进行更新。At the writing of this article, the current version of the Azure Active Directory package is 3.10.305231913, so confirm the latest version and update as needed.

Install-Package Microsoft.IdentityModel.Clients.ActiveDirectory -Version 3.10.305231913

Install-Package Microsoft.Azure.KeyVault

在应用程序代码中,创建一个类来保存 Azure Active Directory 身份验证的方法。In your application code, create a class to hold the method for your Azure Active Directory authentication. 在本示例中,该类名为 UtilsIn this example, that class is called Utils. 添加以下 using 语句:Add the following using statement:

using Microsoft.IdentityModel.Clients.ActiveDirectory;

接下来,添加以下方法,从 Azure Active Directory 检索 JWT 令牌。Next, add the following method to retrieve the JWT token from Azure Active Directory. 为了方便维护,请将硬编码的字符串值移到 Web 或应用程序配置。For maintainability, you might want to move the hard-coded string values into your web or application configuration.

public async static Task<string> GetToken(string authority, string resource, string scope)
{
    var authContext = new AuthenticationContext(authority);

    ClientCredential clientCred = new ClientCredential("<AzureADApplicationClientID>","<AzureADApplicationClientKey>");

    AuthenticationResult result = await authContext.AcquireTokenAsync(resource, clientCred);

    if (result == null)

    throw new InvalidOperationException("Failed to obtain the JWT token");

    return result.AccessToken;
}

添加所需的代码,调用密钥保管库并检索机密值。Add the necessary code to call Key Vault and retrieve your secret value. 首先,必须添加以下 using 语句:First, you must add the following using statement:

using Microsoft.Azure.KeyVault;

添加方法调用,调用密钥保管库并检索机密。Add the method calls to invoke Key Vault and retrieve your secret. 在此方法中,提供在前面步骤中保存的机密 URI。In this method, you provide the secret URI that you saved in a previous step. 请注意如何使用前面创建的 Utils 类中的 GetToken 方法。Note the use of the GetToken method from the Utils class you created previously.

var kv = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(Utils.GetToken));

var sec = kv.GetSecretAsync(<SecretID>).Result.Value;

现在,运行应用程序时,用户应该会向 Azure Active Directory 进行身份验证,并从 Azure Key Vault 中检索机密值。When you run your application, you should now be authenticating to Azure Active Directory and then retrieving your secret value from Azure Key Vault.

使用 Azure 自动化进行密钥轮替Key rotation using Azure Automation

重要

Azure 自动化 Runbook 仍需使用 AzureRM 模块。Azure Automation runbooks still require the use of the AzureRM module.

现在,对于存储为 Key Vault 机密的值,可以设置轮换策略。You are now ready to set up a rotation strategy for the values you store as Key Vault secrets. 可通过多种方式轮换机密:Secrets can be rotated in several ways:

  • 手动轮换As part of a manual process
  • 使用 API 调用以编程方式轮换Programmatically by using API calls
  • 通过 Azure 自动化脚本轮换Through an Azure Automation script

本文结合使用 Azure PowerShell 和 Azure 自动化来更改 Azure 存储帐户的访问密钥。For the purposes of this article, you'll use PowerShell combined with Azure Automation to change an Azure storage account's access key. 然后使用新密钥更新 Key Vault 机密。You'll then update a key vault secret with that new key.

若要允许 Azure 自动化在 Key Vault 中设置机密值,必须获取名为 AzureRunAsConnection 的连接的客户端 ID。To allow Azure Automation to set secret values in your key vault, you must get the client ID for the connection named AzureRunAsConnection. 此连接是建立 Azure 自动化实例时创建的。This connection was created when you established your Azure Automation instance. 若要查找此 ID,请在 Azure 自动化实例中选择“资产”。 To find this ID, select Assets from your Azure Automation instance. 在此处选择“连接”,然后选择“AzureRunAsConnection”服务主体。 From there, select Connections, and then select the AzureRunAsConnection service principal. 记下“ApplicationId”值。 Make note of the ApplicationId value.

Azure 自动化客户端 ID

在“资产”中选择“模块”。 In Assets, select Modules. 选择“库”,然后搜索并导入以下每个模块的更新版本: Select Gallery, and then search for and import updated versions of each of the following modules:

Azure
Azure.Storage
AzureRM.Profile
AzureRM.KeyVault
AzureRM.Automation
AzureRM.Storage

备注

在撰写本文时,只需要针对以下脚本更新上面记下的模块。At the writing of this article, only the previously noted modules needed to be updated for the following script. 如果自动化作业失败,请确认已导入所有必要的模块及其依赖项。If your automation job fails, confirm that you've imported all necessary modules and their dependencies.

检索 Azure 自动化连接的应用程序 ID 之后,必须让 Key Vault 知道此应用程序有权更新保管库中的机密。After you've retrieved the application ID for your Azure Automation connection, you must tell your key vault that this application has permission to update secrets in your vault. 使用以下 PowerShell 命令:Use the following PowerShell command:

Set-AzKeyVaultAccessPolicy -VaultName <vaultName> -ServicePrincipalName <applicationIDfromAzureAutomation> -PermissionsToSecrets Set

接下来,选择 Azure 自动化实例下的“Runbook”,然后选择“添加 Runbook”。 Next, select Runbooks under your Azure Automation instance, and then select Add Runbook. 选择“快速创建”。 Select Quick Create. 为 Runbook 命名,然后选择“PowerShell”作为 Runbook 类型。 Name your runbook, and select PowerShell as the runbook type. 可以添加说明。You can add a description. 最后,选择“创建” 。Finally, select Create.

创建 Runbook

将以下 PowerShell 脚本粘贴在新 Runbook 的编辑器窗格中:Paste the following PowerShell script in the editor pane for your new runbook:

$connectionName = "AzureRunAsConnection"
try
{
    # Get the connection "AzureRunAsConnection"
    $servicePrincipalConnection=Get-AutomationConnection -Name $connectionName         

    "Logging in to Azure..."
    Connect-AzureRmAccount -Environment AzureChinaCloud`
        -ServicePrincipal `
        -TenantId $servicePrincipalConnection.TenantId `
        -ApplicationId $servicePrincipalConnection.ApplicationId `
        -CertificateThumbprint $servicePrincipalConnection.CertificateThumbprint
    "Login complete."
}
catch {
    if (!$servicePrincipalConnection)
    {
        $ErrorMessage = "Connection $connectionName not found."
        throw $ErrorMessage
    } else{
        Write-Error -Message $_.Exception
        throw $_.Exception
    }
}

# Optionally you can set the following as parameters
$StorageAccountName = <storageAccountName>
$RGName = <storageAccountResourceGroupName>
$VaultName = <keyVaultName>
$SecretName = <keyVaultSecretName>

#Key name. For example key1 or key2 for the storage account
New-AzureRmStorageAccountKey -ResourceGroupName $RGName -Name $StorageAccountName -KeyName "key2" -Verbose
$SAKeys = Get-AzureRmStorageAccountKey -ResourceGroupName $RGName -Name $StorageAccountName

$secretvalue = ConvertTo-SecureString $SAKeys[1].Value -AsPlainText -Force

$secret = Set-AzureKeyVaultSecret -VaultName $VaultName -Name $SecretName -SecretValue $secretvalue

在编辑器窗格中,选择“测试”窗格以测试脚本。 In the editor pane, select Test pane to test your script. 正常运行脚本后,可以选择“发布”,并在 Runbook 配置窗格中应用 Runbook 的计划。 After the script runs without error, you can select Publish, and then you can apply a schedule for the runbook in the runbook configuration pane.

密钥保管库审核管道Key Vault auditing pipeline

设置密钥保管库时,可以打开审核功能,收集有关对密钥保管库发出的访问请求的日志。When you set up a key vault, you can turn on auditing to collect logs on access requests made to the key vault. 这些日志存储在指定的 Azure 存储帐户中,可以提取、监视和分析。These logs are stored in a designated Azure storage account and can be pulled out, monitored, and analyzed. 以下方案将使用 Azure Functions、Azure 逻辑应用和 Key Vault 审核日志创建管道,以便在与该 Web 应用的应用 ID 不匹配的应用从保管库检索机密时发送电子邮件。The following scenario uses Azure functions, Azure logic apps, and key-vault audit logs to create a pipeline that sends an email when an app that doesn't match the app ID of the web app retrieves secrets from the vault.

首先,必须对密钥保管库启用日志记录。First, you must enable logging on your key vault. 使用以下 PowerShell 命令。Use the following PowerShell commands. (可以在这篇有关 Key Vault 日志记录的文章中查看完整详细信息。)(You can see the full details in this article about key-vault-logging.)

$sa = New-AzStorageAccount -ResourceGroupName <resourceGroupName> -Name <storageAccountName> -Type Standard\_LRS -Location 'East US'
$kv = Get-AzKeyVault -VaultName '<vaultName>'
Set-AzDiagnosticSetting -ResourceId $kv.ResourceId -StorageAccountId $sa.Id -Enabled $true -Category AuditEvent

启用日志记录后,审核日志将开始存储到指定的存储帐户中。After logging is enabled, audit logs start being stored in the designated storage account. 这些日志包含有关访问密钥保管库的方式、时间和用户的事件。These logs contain events about how and when your key vaults are accessed, and by whom.

备注

在执行 Key Vault 操作 10 分钟后,即可访问日志记录信息。You can access your logging information 10 minutes after the key vault operation. 但通常不用等待这么长时间。It will often be available sooner than that.

下一步是创建 Azure 服务总线队列The next step is to create an Azure Service Bus queue. 此队列是 Key Vault 审核日志的推送位置。This queue is where key-vault audit logs are pushed. 审核日志消息进入队列后,逻辑应用将选择并处理它们。When the audit-log messages are in the queue, the logic app picks them up and acts on them. 使用以下步骤创建服务总线实例:Create a Service Bus instance with the following steps:

  1. 创建服务总线命名空间(如果已有一个可用的命名空间,请跳到步骤 2)。Create a Service Bus namespace (if you already have one that you want to use, skip to step 2).
  2. 在 Azure 门户中浏览到服务总线实例,并选择要在其中创建队列的命名空间。Browse to the Service Bus instance in the Azure portal and select the namespace you want to create the queue in.
  3. 选择“创建资源” > “企业集成” > “服务总线”,并输入所需的详细信息。 Select Create a resource > Enterprise Integration > Service Bus, and then enter the required details.
  4. 通过选择命名空间并选择“连接信息” ,找到服务总线连接信息。Find the Service Bus connection information by selecting the namespace and then selecting Connection Information. 在下一部分需要用到此信息。You'll need this information for the next section.

接下来,创建 Azure 函数以轮询存储帐户中的 Key Vault 日志并选取新的事件。Next, create an Azure function to poll the key vault logs within the storage account and pick up new events. 此函数将按计划触发。This function will be triggered on a schedule.

若要创建 Azure 函数应用,请选择“创建资源” ,在市场中搜索“函数应用” ,并选择“创建” 。To create an Azure function app, select Create a resource, search the marketplace for Function App, and then select Create. 在创建过程中,可以使用现有的托管计划,或创建新的计划。During creation, you can use an existing hosting plan or create a new one. 也可以选择动态托管。You can also opt for dynamic hosting. 有关 Azure Functions 的托管选项的详细信息,请参阅 如何缩放 Azure FunctionsFor more information about the hosting options for Azure Functions, see How to scale Azure Functions.

创建 Azure 函数应用后,转到该应用,然后选择“计时器”作为方案,选择“C#”作为语言。 After the Azure function app is created, go to it, and select the Timer scenario and C# for the language. 然后选择“创建此函数” 。Then select Create this function.

Azure Functions“开始”屏幕边栏选项卡

在“开发”选项卡中,将 run.csx 代码替换为以下内容: On the Develop tab, replace the run.csx code with the following:

#r "Newtonsoft.Json"

using System;
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Auth;
using Microsoft.WindowsAzure.Storage.Blob;
using Microsoft.ServiceBus.Messaging;
using System.Text;

public static void Run(TimerInfo myTimer, TextReader inputBlob, TextWriter outputBlob, TraceWriter log)
{
    log.Info("Starting");

    CloudStorageAccount sourceStorageAccount = new CloudStorageAccount(new StorageCredentials("<STORAGE_ACCOUNT_NAME>", "<STORAGE_ACCOUNT_KEY>"), true);

    CloudBlobClient sourceCloudBlobClient = sourceStorageAccount.CreateCloudBlobClient();

    var connectionString = "<SERVICE_BUS_CONNECTION_STRING>";
    var queueName = "<SERVICE_BUS_QUEUE_NAME>";

    var sbClient = QueueClient.CreateFromConnectionString(connectionString, queueName);

    DateTime dtPrev = DateTime.UtcNow;
    if(inputBlob != null)
    {
        var txt = inputBlob.ReadToEnd();

        if(!string.IsNullOrEmpty(txt))
        {
            dtPrev = DateTime.Parse(txt);
            log.Verbose($"SyncPoint: {dtPrev.ToString("O")}");
        }
        else
        {
            dtPrev = DateTime.UtcNow;
            log.Verbose($"Sync point file didn't have a date. Setting to now.");
        }
    }

    var now = DateTime.UtcNow;

    string blobPrefix = "insights-logs-auditevent/resourceId=/SUBSCRIPTIONS/<SUBSCRIPTION_ID>/RESOURCEGROUPS/<RESOURCE_GROUP_NAME>/PROVIDERS/MICROSOFT.KEYVAULT/VAULTS/<KEY_VAULT_NAME>/y=" + now.Year +"/m="+now.Month.ToString("D2")+"/d="+ (now.Day).ToString("D2")+"/h="+(now.Hour).ToString("D2")+"/m=00/";

    log.Info($"Scanning:  {blobPrefix}");

    IEnumerable<IListBlobItem> blobs = sourceCloudBlobClient.ListBlobs(blobPrefix, true);

    log.Info($"found {blobs.Count()} blobs");

    foreach(var item in blobs)
    {
        if (item is CloudBlockBlob)
        {
            CloudBlockBlob blockBlob = (CloudBlockBlob)item;

            log.Info($"Syncing: {item.Uri}");

            string sharedAccessUri = GetContainerSasUri(blockBlob);

            CloudBlockBlob sourceBlob = new CloudBlockBlob(new Uri(sharedAccessUri));

            string text;
            using (var memoryStream = new MemoryStream())
            {
                sourceBlob.DownloadToStream(memoryStream);
                text = System.Text.Encoding.UTF8.GetString(memoryStream.ToArray());
            }

            dynamic dynJson = JsonConvert.DeserializeObject(text);

            //Required to order by time as they might not be in the file
            var results = ((IEnumerable<dynamic>) dynJson.records).OrderBy(p => p.time);

            foreach (var jsonItem in results)
            {
                DateTime dt = Convert.ToDateTime(jsonItem.time);

                if(dt>dtPrev){
                    log.Info($"{jsonItem.ToString()}");

                    var payloadStream = new MemoryStream(Encoding.UTF8.GetBytes(jsonItem.ToString()));
                    //When sending to ServiceBus, use the payloadStream and set keeporiginal to true
                    var message = new BrokeredMessage(payloadStream, true);
                    sbClient.Send(message);
                    dtPrev = dt;
                }
            }
        }
    }
    outputBlob.Write(dtPrev.ToString("o"));
}

static string GetContainerSasUri(CloudBlockBlob blob)
{
    SharedAccessBlobPolicy sasConstraints = new SharedAccessBlobPolicy();

    sasConstraints.SharedAccessStartTime = DateTime.UtcNow.AddMinutes(-5);
    sasConstraints.SharedAccessExpiryTime = DateTime.UtcNow.AddHours(24);
    sasConstraints.Permissions = SharedAccessBlobPermissions.Read;

    //Generate the shared access signature on the container, setting the constraints directly on the signature.
    string sasBlobToken = blob.GetSharedAccessSignature(sasConstraints);

    //Return the URI string for the container, including the SAS token.
    return blob.Uri + sasBlobToken;
}

备注

更改上面代码中的变量,以指向写入 Key Vault 日志的存储帐户、以前创建的服务总线实例和 Key Vault 存储日志的特定路径。Change the variables in the preceding code to point to your storage account where the key vault logs are written, to the Service Bus instance you created earlier, and to the specific path to the key-vault storage logs.

该函数在写入密钥保管库日志的存储帐户中选取最新日志文件、从该文件中获取最新事件,并将这些事件推送到服务总线队列。The function picks up the latest log file from the storage account where the key vault logs are written, grabs the latest events from that file, and pushes them to a Service Bus queue.

由于单个文件可以包含多个事件,因此应该创建了一个 sync.txt 文件,函数会参照该文件确定所选最后一个事件的时间戳。Because a single file can have multiple events, you should create a sync.txt file that the function also looks at to determine the time stamp of the last event that was picked up. 使用此文件可以确保不会多次推送相同的事件。Using this file ensures that you don't push the same event multiple times.

sync.txt 文件包含上次遇到的事件的时间戳。The sync.txt file contains a time stamp for the last-encountered event. 加载日志时,必须根据其时间戳将其排序,以确保其顺序正确。When the logs are loaded, they must be sorted based on their time stamps to ensure that they're ordered correctly.

在此函数中,我们引用了 Azure Functions 中几个无法现成使用的附加库。For this function, we reference a couple additional libraries that aren't available out of the box in Azure Functions. 若要包含这些库,需要在 Azure Functions 中使用 NuGet 提取它们。To include these libraries, we need Azure Functions to pull them by using NuGet. 在“代码”框下,选择“查看文件”。 Under the Code box, select View Files.

“查看文件”选项

添加包含以下内容的名为 project.json 的文件:Add a file called project.json with the following content:

    {
      "frameworks": {
        "net46":{
          "dependencies": {
                "WindowsAzure.Storage": "7.0.0",
                "WindowsAzure.ServiceBus":"3.2.2"
          }
        }
       }
    }

选择“保存” 后,Azure Functions 将下载所需的二进制文件。After you select Save, Azure Functions will download the required binaries.

切换到“集成”选项卡,为计时器参数指定一个要在函数中使用的有意义名称。Switch to the Integrate tab and give the timer parameter a meaningful name to use within the function. 在以上代码中,函数需要名为 myTimer 的计时器。In the preceding code, the function expects the timer to be called myTimer. 按如下所示为计时器指定 CRON 表达式0 * * * * *Specify a CRON expression for the timer as follows: 0 * * * * *. 此表达式会导致函数一分钟运行一次。This expression will cause the function to run once a minute.

在同一个“集成” 选项卡上,添加类型为“Azure Blob 存储” 的输入。On the same Integrate tab, add an input of the type Azure Blob storage. 此输入会指向 sync.txt 文件,其中包含该函数查看的最后一个事件的时间戳。This input will point to the sync.txt file that contains the time stamp of the last event looked at by the function. 将在函数中使用参数名称访问此输入。This input will be accessed within the function by using the parameter name. 在以上代码中,Azure Blob 存储输入要求参数名称为 inputBlobIn the preceding code, the Azure Blob storage input expects the parameter name to be inputBlob. 选择 sync.txt 文件所在的存储帐户(该存储帐户可以相同,也可以不同)。Select the storage account where the sync.txt file will be located (it could be the same or a different storage account). 在路径字段中,以 {container-name}/path/to/sync.txt 格式提供文件的路径。In the path field, provide the path to the file in the format {container-name}/path/to/sync.txt.

添加一个类型为“Azure Blob 存储” 的输出。Add an output of the type Azure Blob storage. 此输出会指向刚在输入中定义的 sync.txt 文件。This output will point to the sync.txt file you defined in the input. 函数使用此输出写入所查找的最后一个事件的时间戳。This output is used by the function to write the time stamp of the last event looked at. 在上面的代码中,要求此参数名为 outputBlobThe preceding code expects this parameter to be called outputBlob.

函数现已准备就绪。The function is now ready. 确保切换回“开发” 选项卡并保存代码。Make sure to switch back to the Develop tab and save the code. 检查输出窗口中是否有任何编译错误并根据需要进行更正。Check the output window for any compilation errors and correct them as needed. 如果代码可以编译,则代码现在应会每隔一分钟检查 Key Vault 日志,并将所有新事件推送到定义的服务总线队列。If the code compiles, then the code should now be checking the key vault logs every minute and pushing any new events into the defined Service Bus queue. 每次触发该函数时,应该都会看到向日志窗口写入日志记录信息。You should see logging information write out to the log window every time the function is triggered.

Azure 逻辑应用Azure logic app

接下来,必须创建一个 Azure 逻辑应用,用于选择函数推送到服务总线队列的事件、分析内容,并根据匹配的条件发送电子邮件。Next, you must create an Azure logic app that picks up the events that the function is pushing to the Service Bus queue, parses the content, and sends an email based on a condition being matched.

选择“创建资源” > “集成” > “逻辑应用”来创建逻辑应用Create a logic app by selecting Create a resource > Integration > Logic App.

创建逻辑应用后,转到该应用并选择“编辑”。 After the logic app is created, go to it and select Edit. 在逻辑应用编辑器中,选择“服务总线队列” ,并输入服务总线凭据以将其连接到队列。In the logic app editor, select Service Bus Queue and enter your Service Bus credentials to connect it to the queue.

Azure 逻辑应用服务总线

选择“添加条件”。 Select Add a condition. 在条件中,切换到高级编辑器并输入以下代码。In the condition, switch to the advanced editor and enter the following code. APP_ID 替换为 Web 应用的实际应用 ID:Replace APP_ID with the actual app ID of your web app:

@equals('<APP_ID>', json(decodeBase64(triggerBody()['ContentData']))['identity']['claim']['appid'])

如果传入事件中的 appid(这是服务总线消息的正文)不是该应用的 appid,则此表达式实质上将返回 falseThis expression essentially returns false if the appid from the incoming event (which is the body of the Service Bus message) isn't the appid of the app.

现在,在“如果否,则不执行任何操作” 下创建一个操作。Now, create an action under IF NO, DO NOTHING.

在 Azure 逻辑应用中选择操作

对于操作,选择“Office 365 - 发送电子邮件” 。For the action, select Office 365 - send email. false时要发送的电子邮件。Fill out the fields to create an email to send when the defined condition returns false. 如果没有 Office 365,请查看能够实现相同结果的替代方案。If you don't have Office 365, look for alternatives to achieve the same results.

现在端到端管道已创建完毕,它每分钟都会查找一次是否有新的 Key Vault 审核日志。You now have an end-to-end pipeline that looks for new key-vault audit logs once a minute. 它将发现的新日志推送到服务总线队列。It pushes new logs it finds to a Service Bus queue. 新消息进入队列后,会触发逻辑应用。The logic app is triggered when a new message lands in the queue. 如果事件中的 appid 与调用方应用程序的应用 ID 不匹配,该管道将发送电子邮件。If the appid within the event doesn't match the app ID of the calling application, it sends an email.