2018 年 3 月

第 33 卷,第 3 期

Azure - 使用 Azure Key Vault 保护敏感业务信息

作者 Srikantan Sankaran

作为基于云的服务,Azure Key Vault 可便于组织安全地存储敏感业务信息。此服务可方便用户对数据执行加密操作,并提供用于规范对应用程序的访问的策略实现框架,以及使用自身存储的密钥、机密和证书的应用程序 API 模型。Azure Key Vault 提供的 SDK 支持各种设备平台和编程语言,可方便用户选择首选语言,并将这些应用程序作为托管 Web 应用部署到 Azure 应用服务。为了向组织内外用户安全公开这些商业应用程序,Azure Active Directory (Azure AD) 和 Azure Active Directory B2C (Azure AD B2C) 提供了统包实现,这样使用极少或不使用自定义代码,就能对应用程序进行身份验证和授权。本文介绍的解决方案展示了 Azure Key Vault 如何为组织再添一道安全屏障。

用例方案

某中央机构接受了一项任务,即实现解决方案来签发、跟踪和管理机动车辆保险单。此机构在收到保险公司的订单和付款后,生成唯一文档序列号。保险公司在向汽车驾驶员销售机动车辆保险时,直接或通过经纪人将保险单分配到文档序列号。文档序列号对所有保险公司都是唯一的。

此解决方案的目标是,跟踪文档序列号的生命周期。创建的文档序列号只包含它的编号和销售给的保险公司名称。随着业务流程进一步开展,将添加机动车辆注册号、保险单文档编号、客户标识和保险单有效期等附加信息。必须跟踪此记录的所有版本,包括做出的任何更改、更改的日期和时间,以及做出更改的应用程序的标识。

客户应能够以电子方式查看保险单,并安全下载此类信息,以便进行验证和方便参考。

解决方案的体系结构

此解决方案使用 Azure Key Vault,将文档序列号及相关保险单的属性存储为机密。为了再添一道安全屏障,事先会使用 Azure Key Vault 中生成的非对称密钥对存储为机密的数据进行加密。虽然此机密中只捕获了保护和验证每个保险单所需的最起码数据,但 Azure SQL 数据库中还存储了其他支持信息。数据库还对数据实现了约束,以确保已注册的机动车辆只有一个有效保险单编号,以及同一保险单编号尚未用于多个记录等。图 1 展示了此解决方案使用的体系结构。

解决方案体系结构
图 1:解决方案体系结构

我在此解决方案中实现了两个门户应用程序,一个供中央机构和保险公司使用,另一个供购买保险单的客户,以及需要确定保险单有效期的监管机构使用。

管理员门户和客户门户都是 ASP.NET 2.0 Core MVC 应用程序,它们使用 Entity Framework 将已先存储在 Azure Key Vault 中的保险单数据存储在 Azure SQL 数据库中。Azure Key Vault 的 .NET SDK 用于对数据进行加密操作,如创建机密及其版本,以及使用密钥加密和解密机密。管理员门户的用户执行 Azure AD 身份验证,而作为外部用户的客户则使用 Azure AD B2C 进行自注册并登录客户门户。

Azure AD 中的独立服务主体是为管理员门户和客户门户单独创建,而各个策略是为锁定它们在 Azure Key Vault 中可执行的操作而设置。管理员门户策略允许创建密钥和机密,以及执行数据加密和解密等操作。相比之下,客户门户分配有的策略只允许对机密执行“get”操作,以及对检索到的机密执行“decrypt”操作。这样,可以确保各个应用程序对 Azure Key Vault 的访问权限不会超过所需。

为了再添一道安全屏障,先对 Azure Key Vault 中存储为机密的保险单数据进行加密。每次更新机密都会新建一个版本,并暂留旧版数据。此外,还会保留在 Key Vault 中执行的所有操作的审核线索,之所以存档审核线索是为了遵循法定的符合性要求。

Azure Key Vault 中存储的机密的属性包捕获保险单的开始日期和结束日期,用于确定保险单的有效期。机密的标记和内容类型参数用于存储与保险单相关的附加信息。

下面的代码片段展示了如何将属性、标记和内容类型添加到存储为机密的保险单数据中:

SecretAttributes attribs = new SecretAttributes
  {
    Enabled = true,
    Expires = DateTime.UtcNow.AddYears(1),
    NotBefore = DateTime.UtcNow.AddDays(1)
  };
IDictionary<string, string> alltags = new Dictionary<string, string>();
alltags.Add("InsuranceCompany", policydata.Inscompany);
string contentType = "DigitalInsurance";
SecretBundle bundle= await _keyVaultClient.SetSecretAsync(keyVaultUri, 
  policydata.Uidname,encrypteddata,alltags,contentType,attribs);

实现用例方案

接下来,将更深入探究此解决方案实现的用例方案。基本步骤如下:

保险公司购买唯一编码:收到保险公司的订单后,中央机构使用管理员门户生成文档序列号清单,并在 Azure Key Vault 中将它们存储为机密。管理员门户在 Azure Key Vault 中创建第一版机密,再在 Azure SQL 数据库中创建记录。

保险单生成:如果客户购买机动车辆保险单,就会选择上一步中未分配的机密,并添加机动车辆注册号、客户标识、生成的保险单文档编号和保险单有效期等附加信息。在此过程中会在原始机密的基础之上新建包含此附加信息的版本,并且 Azure SQL 数据库中的相应记录也会进行更新。

客户激活保险单:一旦保险单的所有详细信息都被捕获到机密中,就会向客户发送通知(这并不在本文的介绍范围之内),指导客户如何激活保险单。用户可以使用自己的身份证号或在 Azure AD B2C 中存储的凭据在客户门户上进行自注册。登录客户门户后,客户就能看到保险单详情,以及保险单激活选项。激活后,用户便能从客户门户下载 QR 码,并将它的图像粘于被保机动车辆上。

保险单验证:客户或监管机构可以随时使用本机 Xamarin 应用来验证保险单是否与机动车辆相符,只需读取机动车辆上的 QR 码,即可看到保险单详情。此验证无需联网,可以离线完成。如果联网,还可以执行其他验证。本机应用调用在客户门户 MVC 应用程序中公开的 REST API,同时通过 QR 码传递数据。API 先将此类数据与 Azure SQL 数据库中的数据进行匹配,再与 Azure Key Vault 机密中存储的数据进行匹配。

此解决方案的技术方面

现在将深入研究此解决方案使用的源代码和自动化脚本。请注意,本文中共享的代码和脚本目前还不是完整解决方案,也不一定会处理可用于生产环境的应用程序要求的所有验证、异常或最佳做法。而是为了说明特定的技术方面,或指导如何开发成熟的解决方案。

创建和配置 Azure Key Vault:本文随附下载内容中的 PowerShell 脚本文件 PrepareContosoAKV.ps1 和 PrepareContosousersAKV.ps1 用于预配和配置本文中使用的 Key Vault。它们完成下列操作:

  • 为管理员门户和客户门户 ASP.NET MVC 应用程序创建自签名证书(仅用于开发方案),将用于创建 Azure AD 中的服务主体。
  • 在 Azure AD 中创建分配给管理员门户的服务主体。为此服务主体设置的访问策略允许创建和更新密钥和机密,以及执行加密和解密等操作:
# Specify privileges to the vault for the Admin Portal application
Set-AzureRmKeyVaultAccessPolicy -VaultName $vaultName `
  -ObjectId $servicePrincipal.Id `
  -PermissionsToKeys all `
  -PermissionsToSecrets all
  • 在 Azure AD 中创建分配给客户门户的服务主体。为此服务主体设置的访问策略允许对密钥和机密执行 Get 操作,以及解密数据:
# Specify privileges to the vault for the Customer Portal application
Set-AzureRmKeyVaultAccessPolicy -VaultName $vaultName `
  -ObjectId $servicePrincipal.Id `
  -PermissionsToKeys get,list,decrypt `
  -PermissionsToSecrets get,list
  • 请注意,也可以使用 PowerShell 创建这些服务主体,即使用 Azure 应用服务中的托管服务标识功能。这其实才是推荐做法。如需了解详情,请参阅 bit.ly/2BgB6mu 中的指导说明。
  • 创建用于加密和解密机密的密钥。
  • 创建存储对 Azure SQL 数据库的连接字符串的机密。(可以直接在 Azure 门户上完成这一步,其他步骤也可以。)

为了简化实现,此解决方案使用一个 Key Vault,并对所有保险公司和经纪人使用一组密钥来加密和解密数据。在实际情况下,为了再添一道隔离和安全屏障,应为每个保险公司都单独创建 Azure Key Vault 实例,并按区域(举例而言)或其他任何条件分组。这样可以确保一家保险公司维护的数据不被其他保险公司读取,因为它们不共用同一加密密钥。

请注意,Azure Key Vault 中存储的机密大小不得超过 25KB。因此,为了避免膨胀,其中只会存储保险单数据的特定属性,如 ID(文档序列号)、机密名称、用户 ID、保险单编号和保险公司 ID。Visual Studio 2017 解决方案 ContosoInsAuthorityAdminPortal.sln 中的 Entity Insdata.cs 文件包含这些属性。有效开始日期和结束日期、内容类型等其他属性在 Key Vault 中存储为机密属性。

生成管理员门户和客户门户应用程序:请参阅本文随附下载内容中的 Visual Studio 2017 解决方案 ContosoInsAuthorityAdmin­Portal.sln 以获取管理员门户源代码,并参阅 ContosoinsExtPortal.sln 以获取客户门户代码。

管理员门户和客户门户都是使用 ASP.NET Core 2.0 生成,它支持依赖关系注入,以在启动类中向应用程序添加框架服务,如 Entity Framework 集成和自定义服务模块(用于访问 Azure Key Vault API)。

Visual Studio 2017 项目模板提供与 Azure AD 和 Azure AD B2C 的统包集成,用于用户登录和注册体验,以确保对门户应用程序的安全访问。

Azure SQL 数据库的连接字符串存储在 Azure Key Vault 中,并由门户 Web 应用程序在启动时检索。

图 2 中的代码片段展示了如何添加 ASP.NET 2.0 Core 中的依赖关系注入,以用于 Entity Framework Azure AD 身份验证和接入服务提供商、Azure Key Vault 服务 API、访问,以及如何从 appsettings.json 文件中读取应用程序配置数据。

图 2:ASP.NET Core 2.0 MVC 应用程序中的依赖框架

// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
  {
    //Adding the Azure AD integration for User authentication
    services.AddAuthentication(sharedOptions =>
    {
      sharedOptions.DefaultScheme =
        CookieAuthenticationDefaults.AuthenticationScheme;
      sharedOptions.DefaultChallengeScheme =
        OpenIdConnectDefaults.AuthenticationScheme;
    })
    .AddAzureAd(options => Configuration.Bind("AzureAd", options))
    .AddCookie();
    services.AddMvc();
// Add the Key Vault Service Client Connection to the Context Object
    AKVServiceClient servClient =
      new AKVServiceClient(Configuration["AzureKeyVault:ClientIdWeb"],
      Configuration["AzureKeyVault:AuthCertThumbprint"],
      Configuration["AzureKeyVault:VaultName"],­
      Configuration­["AzureKeyVault:KeyName"]);
    services.AddSingleton<AKVServiceClient>(servClient);
// Get the Connection string to Azure SQL Database
// from the secret in ­Azure Key Vault
    string connection = servClient.GetDbConnectionString();
// Add the Azure SQL Database Connection to the Context Object
    services.AddDbContext<ContosoinsauthdbContext>(options =>
      options.UseSqlServer(connection));
    services.AddOptions();
  }

适用于 ASP.NET Core 2.0 的 Azure Key Vault 配置提供程序 (bit.ly/2DfOXeq) 可用作 NuGet 包,以提供统包实现,在应用程序启动时从 Azure Key Vault 中检索所有机密。不过,此解决方案并未使用这项功能,以免在应用程序启动时不必要地加载所有业务数据,即保险单机密以及应用程序所需的其他机密,如用于访问 Azure SQL 数据库的连接字符串。如果使用一个 Key Vault 实例存储应用程序需要的所有连接字符串,并使用单独的 Key Vault 实例来处理业务数据,可以使用此功能。

创建应用服务:管理员门户和客户门户应用程序通过 Visual Studio 2017 中的内置工具部署为 Azure 应用服务 Web 应用。两个 Web 应用都托管在一个应用服务计划中。

开发期间,ASP.NET Core MVC 应用程序可以通过 Visual Studio 2017 进行本地部署。当我在前面介绍的 PowerShell 脚本运行时,生成两个数字证书,每个证书对应一个门户应用程序。.pfx 文件会添加到当前用户的证书存储中。这些文件会嵌入 ASP.NET MVC 应用程序为访问 Azure Key Vault API 而发出的请求中。这些证书的指纹会被添加到相应 ASP.NET MVC 应用程序 Visual Studio 2017 解决方案的 appsettings.json 文件中。

将应用程序部署到 Azure 应用服务时,必须执行以下操作:

  • 在 Azure 门户中将两个 .pfx 文件上传到 Azure 应用服务实例中。
  • 在 Azure 门户的管理员门户和客户门户 Web 应用的“应用设置”边栏选项卡中,创建条目“WEBSITE_LOAD_CERTIFICATES”,并添加相应 .pfx 文件的指纹。

请参阅 bit.ly/2mVEKOq 中的文档,详细了解要执行的步骤。

创建数据库:如果是用于创建解决方案数据库的脚本文件,可以与此解决方案的其他项目一起下载。已对数据库启用透明数据加密 (TDE) 和审核。

创建 Azure AD 和 Azure AD B2C 租户:Azure 订阅中的默认 Azure AD 租户用作访问管理员门户的中央机构内部用户的标识提供者。在此订阅中创建单独的 Azure AD 租户,代表保险公司的用户在其中进行注册。这些用户作为来宾用户添加到默认 Azure AD 租户中,以访问门户应用程序。如果保险公司有自己的 Azure AD 租户,Azure AD B2B 可用于在此订阅中将相应租户与默认 Azure AD 租户进行联盟。

在 Azure 门户中,Azure AD B2C 租户是在 Azure 订阅中进行创建,且策略定义为允许客户进行自注册来访问客户门户。在 Azure AD B2C 配置的标识提供者部分中,此租户内的本地帐户设置为“用户名”,而不是电子邮件地址。为了简化实现,对用户注册禁用电子邮件验证。请参阅 Azure AD B2C 文档,了解如何创建和配置登录和注册体验策略 (bit.ly/2n7Vro9)。

运行应用程序

为了方便大家运行此解决方案,我已在与本文相关的 GitHub 存储库中提供了示例凭据,以供读者登录管理员门户和客户门户。

为了在本文中简化解决方案运行,我尚未实现保险公司购买唯一编码这一步。相反,管理员门户的用户会直接执行下一步,一次性捕获文档序列号、客户信息、保险单和机动车辆详情。

图 3 展示了管理员门户“Contoso Insurance”的登陆页面。可以使用保险公司用户的凭据登录管理员门户,并选择“新建”,以输入新文档的详细信息。文档序列号是由应用程序自动生成,只能在“新建”或“编辑项”表单中查看。

Contoso Insurance 管理员门户登陆页面上的保险单列表
图 3:Contoso Insurance 管理员门户登陆页面上的保险单列表

图 4 展示了存储为机密的不同版本保险单数据。还可以查看其他信息,如内容类型、标记和属性。请注意,机密在存储前就已加密。

Azure 门户上存储为机密的不同版本保险单数据
图 4:Azure 门户上存储为机密的不同版本保险单数据

如果登录客户门户,可以查看所有已购买且可供激活的保险单。“编辑保险单”页提供了保险单激活选项。此选项可以将 Azure SQL 数据库中的保险单状态更新为“活跃”。一旦激活保险单,就可以使用“下载保险单”选项。此选项能够生成保险单数据的 QR 码。

图 5 展示了在客户门户上下载 QR 码的用户体验。还展示了在移动设备上使用应用通过 QR 码读取的 JSON 数据。本机应用会扫描 QR 码,并在屏幕上显示格式化保险单详情,以便用户离线验证。

QR 码生成
图 5:QR 码生成

为了再添一道安全屏障,可以让客户门户使用 Azure Key Vault 中的私钥对 JSON 数据进行签名,并对已签名的数据生成 QR 码。本机移动应用可以随附必需公钥,以便能够先验证已签名的数据,再在设备上显示 JSON 数据以供验证。

客户门户使用 JavaScript QR 码生成器(可通过 GitHub 获取,网址为 bit.ly/2sa3TWy),在门户上生成和显示 QR 码。

保险单验证

可离线或在线验证保险单。离线验证的执行方式包括在移动设备上使用任何 QR 码读取器应用,或使用本机 Xamarin 应用。扫描 QR 码后,设备会以易记方式显示结果以供验证。

相比之下,图 6 展示了使用 Postman 工具 (getpostman.com) 向 MVC 应用程序中的 API 发送的验证请求,返回的验证结果为布尔值。在此示例中,保险单开始日期晚于当前日期,因此验证结果为“false”。 Xamarin 应用有助于用户登录,扫描并查看 QR 码数据,并向此 API 发出执行在线验证的请求。

验证保险单的 REST API 请求
图 6:验证保险单的 REST API 请求

Azure Key Vault 中的所有操作都可以进行审核,并存档日志,以符合法令规定。可以通过 Azure 门户中的“设置”边栏选项卡,对 Azure Key Vault 服务实例启用审核。若要查看日志,请转到针对日志配置的 Azure 存储资源。为了确保只有指定用户才能访问此信息,可以在 Azure 门户中使用基于角色的访问控制 (RBAC)。

此外,还可以通过 Azure 门户中的数据库实例“设置”边栏选项卡,对 Azure SQL 数据库中的所有数据操作启用审核。Azure SQL 数据库中的静态数据默认启用的是透明数据加密。

部署解决方案

若要自行试用此解决方案,可以从 GitHub 存储库 (bit.ly/2DRvwdh) 下载源文件和脚本。若要实现此解决方案,必须使用以下软件:

  • Visual Studio 2017 Preview、Community 或 Enterprise Edition(带更新 3)
  • Azure 订阅
  • Windows PowerShell 脚本编辑器
  • Postman
  • JavaScript QR 码生成器

若要在自己的订阅中部署此解决方案,需要在 PowerShell 脚本已执行且其他资源已在 Azure 订阅中预配后,更新 appsettings.json 文件中的配置条目。GitHub 存储库中提供了这样做的具体步骤,以及本文中的源代码和解决方案文件。非常感谢 Microsoft CSE 团队的 Bindu Chinnasamy 帮助生成本文随附的解决方案。

总结

Azure Key Vault 为企业提供了高效实用的平台,让企业可以使用行业标准算法和技术来执行加密操作,从而安全地管理敏感信息。借助它,开发人员可以使用自己已习惯的平台和语言的 SDK。凭借这一点,再加上 Azure 中的一组丰富附加服务(如 Azure 应用服务、Azure AD 和 Azure B2C),以及这些服务提供的周密工具支持,开发人员可以专注于生成核心业务功能,从而大大减少开发和部署端到端解决方案所需的时间。

在本文的续篇中,我将介绍如何使用 Docker 容器和 Kubernetes 将相同的应用程序部署到 Azure 容器服务,且没有重大改变。


Srikantan Sankaran 是总部位于班加罗尔的印度 One Commercial Partner 团队中的一名主要技术宣传员。他与印度的众多独立软件开发商合作,帮助他们在 Microsoft Azure 上生成和部署解决方案。请通过 sansri@microsoft.com 与他联系。**

衷心感谢以下 Microsoft 技术专家对本文的审阅:Frank Hokhold、Bindu Chinnasamy
非常感谢 Frank Hokhold 对本文的审阅。Frank 是 Azure Key Vault 团队的开发人员体验项目经理。
也非常感谢 Microsoft CSE 团队的 Bindu Chinnasamy 帮助生成本文随附的解决方案。


在 MSDN 杂志论坛讨论这篇文章