向 ASP.NET 和 Azure 应用服务部署密码和其他敏感数据的最佳做法

作者: 里克·安德森

本教程演示如何代码安全地存储和访问安全信息。 最重要的是,不应在源代码中存储密码或其他敏感数据,不应在开发和测试模式下使用生产机密。

示例代码是一个简单的 WebJob 控制台应用和一个 ASP.NET MVC 应用,需要访问数据库连接字符串密码、Twilio、Google 和 SendGrid 安全密钥。

此外还提到本地设置和 PHP。

在开发环境中使用密码

教程经常在源代码中显示敏感数据,希望注意,不应在源代码中存储敏感数据。 例如,我的 ASP.NET 包含短信和电子邮件 2FA 的 MVC 5 应用教程在 web.config 文件中显示了以下内容:

</connectionStrings>
   <appSettings>
      <add key="webpages:Version" value="3.0.0.0" />
      <!-- Markup removed for clarity. -->
      
      <!-- SendGrid-->
      <add key="mailAccount" value="account" />
      <add key="mailPassword" value="my password" />
      <!-- Twilio-->
      <add key="TwilioSid" value="My SID" />
      <add key="TwilioToken" value="My Token" />
      <add key="TwilioFromPhone" value="+12065551234" />

      <add key="GoogClientID" value="1234.apps.googleusercontent.com" />
      <add key="GoogClientSecret" value="My GCS" />
   </appSettings>
 <system.web>

web.config 文件是源代码,因此不应将这些机密存储在该文件中。 幸运的是,该 <appSettings> 元素具有一个 file 属性,可用于指定包含敏感应用配置设置的外部文件。 只要外部文件未签入源树,就可以将所有机密移动到外部文件。 例如,在以下标记中,AppSettingsSecrets.config 文件包含所有应用机密:

</connectionStrings>
   <appSettings file="..\..\AppSettingsSecrets.config">      
      <add key="webpages:Version" value="3.0.0.0" />
      <add key="webpages:Enabled" value="false" />
      <add key="ClientValidationEnabled" value="true" />
      <add key="UnobtrusiveJavaScriptEnabled" value="true" />      
   </appSettings>
  <system.web>

外部文件中的标记(此示例中的 AppSettingsSecrets.config)与 Web.config 文件中的标记相同:

<appSettings>   
   <!-- SendGrid-->
   <add key="mailAccount" value="My mail account." />
   <add key="mailPassword" value="My mail password." />
   <!-- Twilio-->
   <add key="TwilioSid" value="My Twilio SID." />
   <add key="TwilioToken" value="My Twilio Token." />
   <add key="TwilioFromPhone" value="+12065551234" />

   <add key="GoogClientID" value="1.apps.googleusercontent.com" />
   <add key="GoogClientSecret" value="My Google client secret." />
</appSettings>

ASP.NET 运行时将外部文件的内容与 appSettings> 元素中的<标记合并。 如果找不到指定的文件,运行时会忽略文件属性。

警告

安全性 - 不要将 机密 .config 文件添加到项目或将其签入源代码管理。 默认情况下,Visual Studio 将 Build Action 设置为 Content,这意味着文件已部署。 有关详细信息,请参阅为什么无法部署项目文件夹中的所有文件?尽管可以对机密 .config 文件使用任何扩展名,但最好保留 .config,因为 IIS 不提供配置文件。 另请注意,AppSettingsSecrets.config 文件是 Web.config 文件中的两个目录级别,因此它完全脱离解决方案目录。 通过将文件移出解决方案目录,“git add *”不会将其添加到存储库。

在开发环境中使用连接字符串

Visual Studio 创建新的使用 LocalDB 的 ASP.NET 项目。 LocalDB 专为开发环境创建。 它不需要密码,因此无需执行任何操作,以防止机密签入源代码。 某些开发团队使用需要密码的完整 SQL Server 版本(或其他 DBMS)。

可以使用该 configSource 属性替换整个 <connectionStrings> 标记。 <appSettings> file与合并标记的属性不同,该configSource属性将替换标记。 以下标记显示 configSource web.config 文件中的属性

<connectionStrings configSource="ConnectionStrings.config">
</connectionStrings>

注意

如果使用configSource上述属性将连接字符串移动到外部文件,并且让 Visual Studio 创建新的网站,则无法检测到你正在使用数据库,并且从 Visual Studio 发布到 Azure 时,无法选择配置数据库。 如果使用该 configSource 属性,可以使用 PowerShell 创建和部署网站和数据库,也可以在发布之前在门户中创建网站和数据库。

警告

安全性 - 与 AppSettingsSecrets.config 文件不同,外部连接字符串s 文件必须与根 web.config 文件位于同一目录中,因此必须采取预防措施,以确保不会将其签入源存储库。

注意

机密文件的安全警告: 最佳做法是不要在测试和开发中使用生产机密。 在测试或开发中使用生产密码会泄露这些机密。

WebJobs 控制台应用

控制台应用使用的 app.config 文件不支持相对路径,但它支持绝对路径。 可以使用绝对路径将机密移出项目目录。 以下标记显示了 C:\secrets\AppSettingsSecrets.config 文件中的机密,以及 app.config 文件中的非敏感数据

<configuration>
  <appSettings file="C:\secrets\AppSettingsSecrets.config">
    <add key="TwitterMaxThreads" value="24" />
    <add key="StackOverflowMaxThreads" value="24" />
    <add key="MaxDaysForPurge" value="30" />
  </appSettings>
</configuration>

将机密部署到 Azure

将 Web 应用部署到 Azure 时, 不会部署 AppSettingsSecrets.config 文件(这就是所需内容)。 可以转到 Azure 管理门户 并手动设置它们,以便执行以下操作:

  1. 转到 https://portal.azure.comAzure 凭据并登录。
  2. 单击“浏览>Web 应用,然后单击 Web 应用的名称。
  3. 单击“ 所有设置 > ”应用程序设置

应用设置连接字符串值会覆盖 web.config 文件中的相同设置。 在我们的示例中,我们没有将这些设置部署到 Azure,但如果这些密钥位于 web.config 文件中,则门户上显示的设置将优先。

最佳做法是遵循 DevOps 工作流 并使用 Azure PowerShell (或其他框架(如 ChefPuppet)在 Azure 中自动设置这些值。 以下 PowerShell 脚本使用 Export-CliXml 将加密机密导出到磁盘:

param(
  [Parameter(Mandatory=$true)] 
  [String]$Name,
  [Parameter(Mandatory=$true)]
  [String]$Password)

$credPath = $PSScriptRoot + '\' + $Name + ".credential"
$PWord = ConvertTo-SecureString –String $Password –AsPlainText -Force 
$Credential = New-Object –TypeName `
System.Management.Automation.PSCredential –ArgumentList $Name, $PWord
$Credential | Export-CliXml $credPath

在上面的脚本中,“Name”是密钥的名称,例如“FB_AppSecret”或“TwitterSecret”。 可以在浏览器中查看脚本创建的“.credential”文件。 以下代码片段测试每个凭据文件并设置命名 Web 应用的机密:

Function GetPW_fromCredFile { Param( [String]$CredFile )
  $Credential = GetCredsFromFile $CredFile
  $PW = $Credential.GetNetworkCredential().Password  
  # $user just for debugging.
  $user = $Credential.GetNetworkCredential().username 
  Return $PW
}	
$AppSettings = @{	
  "FB_AppSecret"     = GetPW_fromCredFile "FB_AppSecret.credential";
  "GoogClientSecret" = GetPW_fromCredFile "GoogClientSecret.credential";
  "TwitterSecret"    = GetPW_fromCredFile "TwitterSecret.credential";
}
Set-AzureWebsite -Name $WebSiteName -AppSettings $AppSettings

警告

安全性 - 不要在 PowerShell 脚本中包含密码或其他机密,这样做会破坏使用 PowerShell 脚本部署敏感数据的目的。 Get-Credential cmdlet 提供了一种安全机制来获取密码。 使用 UI 提示可以阻止泄露密码。

部署 DB 连接字符串

DB 连接字符串的处理方式与应用设置类似。 如果从 Visual Studio 部署 Web 应用,则会为你配置连接字符串。 可以在门户中验证这一点。 建议使用 PowerShell 设置连接字符串。

PHP 说明

由于应用设置连接字符串的键值对都存储在Azure App 服务上的环境变量中,因此使用任何 Web 应用框架(如 PHP)的开发人员可以轻松检索这些值。 请参阅 Stefan Schackow 的 Windows Azure 网站:应用程序字符串和连接字符串的工作原理博客文章,其中显示了用于读取应用设置和连接字符串的 PHP 代码片段。

本地服务器的说明

如果要部署到本地 Web 服务器,可以通过加密配置文件的配置部分来帮助保护机密。 或者,可以使用 Azure 网站建议的相同方法:将开发设置保留在配置文件中,并将环境变量值用于生产设置。 但是,在这种情况下,你必须为 Azure 网站中自动执行的功能编写应用程序代码:从环境变量检索设置并使用这些值代替配置文件设置,或者在找不到环境变量时使用配置文件设置。

其他资源

请参阅 Stefan Schackow 的 Windows Azure 网站:应用程序字符串和连接字符串的工作原理

特别感谢巴里·多兰斯( @blowdart )和卡洛斯·法雷的审查。