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

作者 :Rick Anderson

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

示例代码是一个简单的 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 创建新的 ASP.NET 使用 LocalDB 的项目。 LocalDB 是专门为开发环境创建的。 它不需要密码,因此无需执行任何操作即可防止机密签入源代码。 某些开发团队使用完整版本的 SQL Server (或其他需要密码的 DBMS) 。

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

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

注意

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

警告

安全性 - 与 AppSettingsSecrets.config 文件不同,外部连接字符串文件必须与根 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.com,然后使用 Azure 凭据登录。
  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 提示可以防止密码泄露。

部署数据库连接字符串

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

PHP 说明

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

本地服务器说明

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

其他资源

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

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