在Asp.NET Core中如何優(yōu)雅的管理用戶機(jī)密數(shù)據(jù)
本文轉(zhuǎn)載自微信公眾號(hào)「DotNET技術(shù)圈」,作者鄒溪源。轉(zhuǎn)載本文請(qǐng)聯(lián)系DotNET技術(shù)圈公眾號(hào)。
背景
回顧
在軟件開發(fā)過程中,使用配置文件來管理某些對(duì)應(yīng)用程序運(yùn)行中需要使用的參數(shù)是常見的作法。
在早期VB/VB.NET時(shí)代,經(jīng)常使用.ini文件來進(jìn)行配置管理;而在.NET FX開發(fā)中,我們則傾向于使用web.config文件,通過配置appsetting的配置節(jié)來處理;而在.NET Core開發(fā)中,我們有了新的基于json格式的appsetting.json文件。
無論采用哪種方式,其實(shí)配置管理從來都是一件看起來簡(jiǎn)單,但影響非常深遠(yuǎn)的基礎(chǔ)性工作。尤其是配置的安全性,貫穿應(yīng)用程序的始終,如果沒能做好安全性問題,極有可能會(huì)給系統(tǒng)帶來不可控的風(fēng)向。
源代碼比配置文件安全么?
有人以為把配置存放在源代碼中,可能比存放在明文的配置文件中似乎更安全,其實(shí)是“皇帝的新裝”。
在前不久,筆者的一位朋友就跟我說了一段故事:他說一位同事在離職后,直接將曾經(jīng)寫過的一段代碼上傳到github的公共倉(cāng)庫,而這段代碼中包含了某些涉及到原企業(yè)的機(jī)密數(shù)據(jù),還好被github的安全機(jī)制提前發(fā)現(xiàn)而及時(shí)終止了該行為,否則后果不堪設(shè)想。
于是,筆者順手查了一下由于有意或無意泄露企業(yè)機(jī)密,造成企業(yè)損失的案例,發(fā)現(xiàn)還真不少。例如大疆前員工通過 Github 泄露公司源代碼,被罰 20 萬、獲刑半年[1] 這起案件,也是一個(gè)典型的案例。
該員工離職后,將包含關(guān)鍵配置信息的源代碼上傳到github的公共倉(cāng)庫,被黑客利用,使得大量用戶私人數(shù)據(jù)被黑客獲取,該前員工最終被刑拘。
大疆前員工通過Github泄露公司源代碼,被罰20萬、獲刑半年
圖片來源: http://www.digitalmunition.com/WhyIWalkedFrom3k.pdf[2]
大部分IT公司都會(huì)在入職前進(jìn)行背景調(diào)查,而一旦有案底,可能就已經(jīng)與許多IT公司無緣;即便是成為創(chuàng)業(yè)者,也可能面臨無法跟很多正規(guī)企業(yè)合作的問題。
小結(jié)
所以,安全性問題不容小覷,哪怕時(shí)間再忙,也不要急匆匆的就將數(shù)據(jù)庫連接字符串或其他包含敏感信息的內(nèi)容輕易的記錄在源代碼或配置文件中。在這個(gè)點(diǎn)上,一旦出現(xiàn)問題,往往都是非常嚴(yán)重的問題。
如何實(shí)現(xiàn)
在.NET FX時(shí)代,我們可以使用對(duì)web.config文件的關(guān)鍵配置節(jié)進(jìn)行加密的方式,來保護(hù)我們的敏感信息,在.NET Core中,自然也有這些東西,接下來我將簡(jiǎn)述在開發(fā)環(huán)境和生產(chǎn)環(huán)境下不同的配置加密手段,希望能夠給讀者帶來啟迪。
開發(fā)環(huán)境
在開發(fā)環(huán)境下,我們可以使用visual studio 工具提供的用戶機(jī)密管理器,只需0行代碼,即可輕松完成關(guān)鍵配置節(jié)的處理。
機(jī)密管理器概述
根據(jù)微軟官方文檔[3] 的描述:
ASP.NET Core 機(jī)密管理器[4]工具提供了開發(fā)過程中在源代碼外部保存機(jī)密的另一種方法 。若要使用機(jī)密管理器工具,請(qǐng)?jiān)陧?xiàng)目文件中安裝包 Microsoft.Extensions.Configuration.SecretManager 。如果該依賴項(xiàng)存在并且已還原,則可以使用 dotnet user-secrets 命令來通過命令行設(shè)置機(jī)密的值。這些機(jī)密將存儲(chǔ)在用戶配置文件目錄中的 JSON 文件中(詳細(xì)信息隨操作系統(tǒng)而異),與源代碼無關(guān)。
機(jī)密管理器工具設(shè)置的機(jī)密是由使用機(jī)密的項(xiàng)目的 UserSecretsId 屬性組織的。因此,必須確保在項(xiàng)目文件中設(shè)置 UserSecretsId 屬性,如下面的代碼片段所示。默認(rèn)值是 Visual Studio 分配的 GUID,但實(shí)際字符串并不重要,只要它在計(jì)算機(jī)中是唯一的。
- <PropertyGroup>
- <UserSecretsId>UniqueIdentifyingString</UserSecretsId>
- </PropertyGroup>
Secret Manager工具允許開發(fā)人員在開發(fā)ASP.NET Core應(yīng)用程序期間存儲(chǔ)和檢索敏感數(shù)據(jù)。敏感數(shù)據(jù)存儲(chǔ)在與應(yīng)用程序源代碼不同的位置。
由于Secret Manager將秘密與源代碼分開存儲(chǔ),因此敏感數(shù)據(jù)不會(huì)提交到源代碼存儲(chǔ)庫。但機(jī)密管理器不會(huì)對(duì)存儲(chǔ)的敏感數(shù)據(jù)進(jìn)行加密,因此不應(yīng)將其視為可信存儲(chǔ)。
敏感數(shù)據(jù)作為鍵值對(duì)存儲(chǔ)在JSON文件中。最好不要在開發(fā)和測(cè)試環(huán)境中使用生產(chǎn)機(jī)密。查看引文[5]。
存放位置
在windows平臺(tái)下,機(jī)密數(shù)據(jù)的存放位置為:
- %APPDATA%\Microsoft\UserSecrets\\secrets.json
而在Linux/MacOs平臺(tái)下,機(jī)密數(shù)據(jù)的存放位置為:
- ~/.microsoft/usersecrets/<user_secrets_id>/secrets.json
在前面的文件路徑中, `user_secrets_id`將替換UserSecretsId為.csproj
文件中指定的值。
在Windows環(huán)境下使用機(jī)密管理器
在windows下,如果使用Visual Studio2019作為主力開發(fā)環(huán)境,只需在項(xiàng)目右鍵單擊,選擇菜單【管理用戶機(jī)密】,即可添加用戶機(jī)密數(shù)據(jù)。
在管理用戶機(jī)密數(shù)據(jù)中,添加的配置信息和傳統(tǒng)的配置信息沒有任何區(qū)別。
- { "ConnectionStrings": { "Default": "Server=xxx;Database=xxx;User ID=xxx;Password=xxx;" } }
我們同樣也可以使用IConfiguration的方式、IOptions的方式,進(jìn)行配置的訪問。
在非Windows/非Visual Studio環(huán)境下使用機(jī)密管理器
完成安裝dotnet-cli后,在控制臺(tái)輸入
- dotnet user-secrets init
前面的命令將在UserSecretsId .csproj 文件的PropertyGroup中添加
.csproj
一個(gè)元素。 UserSecretsId是對(duì)項(xiàng)目是唯一的Guid值。
- <PropertyGroup>
- <TargetFramework>netcoreapp3.1</TargetFramework>
- <UserSecretsId>79a3edd0-2092-40a2-a04d-dcb46d5ca9ed</UserSecretsId>
- </PropertyGroup>
設(shè)置機(jī)密
- dotnet user-secrets set "Movies:ServiceApiKey" "12345"
列出機(jī)密
- dotnet user-secrets list
刪除機(jī)密
- dotnet user-secrets remove "Movies:ConnectionString"
清除所有機(jī)密
- dotnet user-secrets clear
生產(chǎn)環(huán)境
機(jī)密管理器為開發(fā)者在開發(fā)環(huán)境下提供了一種保留機(jī)密數(shù)據(jù)的方法,但在開發(fā)環(huán)境下是不建議使用的,如果想在生產(chǎn)環(huán)境下,對(duì)機(jī)密數(shù)據(jù)進(jìn)行保存該怎么辦?
按照微軟官方文檔的說法,推薦使用Azure Key Vault [6] 來保護(hù)機(jī)密數(shù)據(jù),但。。我不是貴云的用戶(當(dāng)然,買不起貴云不是貴云太貴,而是我個(gè)人的問題[手動(dòng)狗頭]圖片圖片)。
其次,與Azure Key Valut類似的套件,例如其他云,差不多都有,所以都可以為我們所用。
但。。如果您如果跟我一樣,不想通過第三方依賴的形式來解決這個(gè)問題,那不如就用最簡(jiǎn)單的辦法,例如AES加密。
使用AES加密配置節(jié)
該方法與平時(shí)使用AES對(duì)字符串進(jìn)行加密和解密的方法并無區(qū)別,此處從略。
使用數(shù)據(jù)保護(hù)Api(DataProtect Api實(shí)現(xiàn))
在平時(shí)開發(fā)過程中,能夠動(dòng)手?jǐn)]AES加密是一種非常好的習(xí)慣,而微軟官方提供的數(shù)據(jù)保護(hù)API則將這個(gè)過程進(jìn)一步簡(jiǎn)化,只需調(diào)Api即可完成相應(yīng)的數(shù)據(jù)加密操作。
關(guān)于數(shù)據(jù)保護(hù)api, Savorboard[7] 大佬曾經(jīng)寫過3篇博客討論這個(gè)技術(shù)問題,大家可以參考下面的文章來獲取信息。
(接下來我要貼代碼了,如果沒興趣,請(qǐng)出門左拐,代碼不能完整運(yùn)行,查看代碼來源[11])
首先,注入配置項(xiàng)
- public static IServiceCollection AddProtectedConfiguration(this IServiceCollection services, string directory)
- {
- services
- .AddDataProtection()
- .PersistKeysToFileSystem(new DirectoryInfo(directory))
- .UseCustomCryptographicAlgorithms(new ManagedAuthenticatedEncryptorConfiguration
- {
- EncryptionAlgorithmType = typeof(Aes),
- EncryptionAlgorithmKeySize = 256,
- ValidationAlgorithmType = typeof(HMACSHA256)
- });
- ;
- return services;
- }
其次,實(shí)現(xiàn)對(duì)配置節(jié)的加/解密。(使用AES算法的數(shù)據(jù)保護(hù)機(jī)制)
- public class ProtectedConfigurationSection : IConfigurationSection
- {
- private readonly IDataProtectionProvider _dataProtectionProvider;
- private readonly IConfigurationSection _section;
- private readonly Lazy<IDataProtector> _protector;
- public ProtectedConfigurationSection(
- IDataProtectionProvider dataProtectionProvider,
- IConfigurationSection section)
- {
- _dataProtectionProvider = dataProtectionProvider;
- _section = section;
- _protector = new Lazy<IDataProtector>(() => dataProtectionProvider.CreateProtector(section.Path));
- }
- public IConfigurationSection GetSection(string key)
- {
- return new ProtectedConfigurationSection(_dataProtectionProvider, _section.GetSection(key));
- }
- public IEnumerable<IConfigurationSection> GetChildren()
- {
- return _section.GetChildren()
- .Select(x => new ProtectedConfigurationSection(_dataProtectionProvider, x));
- }
- public IChangeToken GetReloadToken()
- {
- return _section.GetReloadToken();
- }
- public string this[string key]
- {
- get => GetProtectedValue(_section[key]);
- set => _section[key] = _protector.Value.Protect(value);
- }
- public string Key => _section.Key;
- public string Path => _section.Path;
- public string Value
- {
- get => GetProtectedValue(_section.Value);
- set => _section.Value = _protector.Value.Protect(value);
- }
- private string GetProtectedValue(string value)
- {
- if (value == null)
- return null;
- return _protector.Value.Unprotect(value);
- }
- }
再次,在使用前,先將待加密的字符串轉(zhuǎn)換成BASE64純文本,然后再使用數(shù)據(jù)保護(hù)API對(duì)數(shù)據(jù)進(jìn)行處理,得到處理后的字符串。
- private readonly IDataProtectionProvider _dataProtectorTokenProvider;
- public TokenAuthController( IDataProtectionProvider dataProtectorTokenProvider)
- {
- }
- [Route("encrypt"), HttpGet, HttpPost]
- public string Encrypt(string section, string value)
- {
- var protector = _dataProtectorTokenProvider.CreateProtector(section);
- return protector.Protect(value);
- }
再替換配置文件中的對(duì)應(yīng)內(nèi)容。
- {
- "ConnectionStrings": {
- "Default": "此處是加密后的字符串"
- }
- }
然后我們就可以按照平時(shí)獲取IOptions的方式來獲取了。
問題
公眾號(hào)【DotNET騷操作】號(hào)主【周杰】同學(xué)提出以下觀點(diǎn):
1、在生產(chǎn)環(huán)境下,使用AES加密,其實(shí)依然是一種不夠安全的行為,充其量也就能忽悠下產(chǎn)品經(jīng)理,畢竟幾條簡(jiǎn)單的語句,就能把機(jī)密數(shù)據(jù)dump出來。
也許在這種情況下,我們應(yīng)該優(yōu)先考慮accessKeyId/accessSecret,盡量通過設(shè)置多級(jí)子賬號(hào),通過授權(quán)Api的機(jī)制來管理機(jī)密數(shù)據(jù),而不是直接暴露類似于數(shù)據(jù)庫連接字符串這樣的關(guān)鍵配置信息。另外,應(yīng)該定期更換數(shù)據(jù)庫的密碼,盡量將類似的問題可能造成的風(fēng)險(xiǎn)降到最低。數(shù)據(jù)保護(hù)api也提供的類似的機(jī)制,使得開發(fā)者能夠輕松的管理機(jī)密數(shù)據(jù)的時(shí)效性問題。
2、配置文件放到CI/CD中,發(fā)布的時(shí)候在CI/CD中進(jìn)行組裝,然后運(yùn)維只是負(fù)責(zé)管理CI/CD的賬戶信息,而最高機(jī)密數(shù)據(jù),則由其他人負(fù)責(zé)配置。
嗯,我完全同意他的第二種做法,另外考慮到由于運(yùn)維同樣有可能會(huì)有意無意泄露機(jī)密數(shù)據(jù),所以如果再給運(yùn)維配備一本《刑法》,并讓他日常補(bǔ)習(xí)【侵犯商業(yè)秘密罪】相關(guān)條款,這個(gè)流程就更加閉環(huán)了。
結(jié)語
本文簡(jiǎn)述了在.NET Core中,如何在開發(fā)環(huán)境下使用用戶機(jī)密管理器、在生產(chǎn)環(huán)境下使用AES+IDataProvider的方式來保護(hù)我們的用戶敏感數(shù)據(jù)。由于時(shí)間倉(cāng)促,如有考慮不周之處,還請(qǐng)各位大佬批評(píng)指正。
References
[1] 大疆前員工通過 Github 泄露公司源代碼,被罰 20 萬、獲刑半年: https://www.infoq.cn/article/RZzfel1m6-h8pSK8TTC9
[2]http://www.digitalmunition.com/WhyIWalkedFrom3k.pdf: http://www.digitalmunition.com/WhyIWalkedFrom3k.pdf
[3] 微軟官方文檔: https://docs.microsoft.com/zh-cn/dotnet/architecture/microservices/secure-net-microservices-web-applications/developer-app-secrets-storage
[4] 機(jī)密管理器: https://docs.microsoft.com/zh-cn/aspnet/core/security/app-secrets#secret-manager
[5] 查看引文: https://nvisium.com/blog/2019/05/02/Dev-Secrets-and-the-ASP-NET-Core-Secret-Manager.html
[6] Azure Key Vault : https://docs.microsoft.com/zh-cn/dotnet/architecture/microservices/secure-net-microservices-web-applications/azure-key-vault-protects-secrets
[7] Savorboard: https://home.cnblogs.com/u/savorboard/
[8] ASP.NET Core 數(shù)據(jù)保護(hù)(Data Protection 集群場(chǎng)景)【上】: https://www.cnblogs.com/savorboard/p/dotnetcore-data-protection.html
[9] ASP.NET Core 數(shù)據(jù)保護(hù)(Data Protection 集群場(chǎng)景)【中】: https://www.cnblogs.com/savorboard/p/dotnet-core-data-protection.html
[10] ASP.NET Core 數(shù)據(jù)保護(hù)(Data Protection 集群場(chǎng)景)【下】: https://www.cnblogs.com/savorboard/p/dotnetcore-data-protected-farm.html
[11] 查看代碼: https://stackoverflow.com/questions/36062670/encrypted-configuration-in-asp-net-core
本文首發(fā)于溪源的個(gè)人博客:https://www.techq.xyz/2020/06/09/%E6%8A%80%E6%9C%AF/how-to-manage-user-secret-in-develop-and-production/