Wstęp
Tutoriale wszędzie pokazują jak świetnie umieszczać ustawienia aplikacji w pliku appsettings.json, a sekrety w secrets.json. To oczywiście pewne uproszczenie, bo w prawdziwym świecie sekretów raczej nie trzyma się w appsettings.json.
Jeśli jednak masz swój własny mały projekt i znalazłeś już hosting .NET (np. ten), może cię kusić wrzucenie sekretów do appsettings. I nawet wszystko działa. Raczej nie powinieneś tego robić (w zależności od wagi projektu i sekretów które udostępniasz), ale działa.
A co zrobić w aplikacji desktopowej (lub mobilnej)? Gdzie po prostu nie możesz trzymać sekretów na urządzeniu użytkownika?
Niestety nie mam dla ciebie dobrej wiadomości – prosto się nie da.
Kiedyś tworzyłem swoje własne rozwiązania – jakieś szyfrowania używające Data Protection API (DAPI), cuda na kiju i bum gdzieś do rejestru. To też nie jest dobre rozwiązanie do trzymania stałych sekretów. Jest lepsze – KeyVault.
W tym artykule opisuję jak trzymać sekrety w aplikacji webowej i jak się do nich dobrać z apki natywnej.
Czym jest Azure KeyVault?
To bardzo tania (serio, nawet student może sobie na to pozwolić) usługa Azure’owa, która służy do przechowywania danych wrażliwych. Dane są szyfrowane i trzymane na serwerach Microsoftu. Także bezpieczeństwo przede wszystkim.
Niestety nie można bezpiecznie dobrać się do KeyVault’a z aplikacji natywnej. Spędziłem naprawdę sporo czasu na szukaniu takiego rozwiązania, ale się nie da.
Musisz posłużyć się pośrednikiem. To może być Azure Function lub twoje własne, małe WebAPI. W tym artykule pokażę ci sposób z WebApi.
Wymagania
- konto z subskrypcją na Azure (przez pierwszy rok możesz mieć za darmo; potem używanie KeyVault jest naprawdę tanie – €0,029/10 000 transakcji (https://azure.microsoft.com/pl-pl/pricing/details/key-vault/).
- dostęp do serwera (spokojnie może to być współdzielony hosting) – .NETCore, php, cokolwiek.
- Certyfikat SSL na serwerze (może być nawet darmowy Let’s Encrypt)
Scenariusz
Pobieranie sekretów z KeyVault przez aplikację natywną polega na:
- wysłaniu żądania z aplikacji natywnej do twojego WebAPI
- WebAPI uwierzytelnia się w KeyVault i pobiera z niego sekrety
- WebAPI odsyła ci sekret
Oczywiście, ze względów bezpieczeństwa, komunikacja między aplikacją natywną i WebAPI musi być szyfrowana (SSL/TLS) i w jakiś sposób autoryzowana. Taka autoryzacja zależy w dużej mierze od konkretnego rozwiązania, więc pominę ten aspekt.
Niestety NIE MA innej drogi jeśli chodzi o aplikacje natywne. Zawsze musi być po drodze WebAPI (ewentualnie Azure Function).
Żeby WebAPI mogło się komunikować z KeyVaultem, musi się do niego uwierzytelnić. Można to zrobić na dwa sposoby:
- przez certyfikat SSL – jeśli WebAPI jest hostowane POZA Azure WebService
- przez Managed Identity, jeśli WebAPI jest hostowane na Azure WebService
Pokażę ci obie opcje.
Tworzenie infrastruktury w Azure
Jak już wspominałem wcześniej, musisz posiadać konto i subskrypcję na Azure.
Jeśli znasz ten portal, to utwórz sobie zasób KeyVault i zarejestruj swoje WebApi w AAD. Jeśli twoje WebApi ma być hostowane na Azure, utwórz dodatkowo WebService dla niego.
Dla nieobeznanych z Azure…
Rejestracja WebApi w Azure
Niezależnie od tego, czy twoje WebApi będzie na serwerze zewnętrznym, czy hostowane w Azure, musisz je zarejestrować w AAD (Azure Active Directory). AAD to darmowa usługa, która pozwala m.in. na rejestrowanie aplikacji. Przydaje się to do wielu rzeczy, m.in. przy używaniu logowania Microsoft. W naszym przypadku rejestracja WebApi umożliwi korzystanie z zasobów KeyVault.
- Wejdź na stronę https://portal.azure.com
- Nie logując się do subskrypcji, wybierz Azure Active Directory
- Z lewego menu wybierz opcję App Registrations
- Następnie New Registration
- W wyświetlonym oknie podaj nazwę aplikacji, a w Supported Account Types wybierz
Accounts in any organizational directory (Any Azure AD directory - Multitenant) and personal Microsoft accounts (e.g. Skype, Xbox)
. Pozwoli to na dostęp do aplikacji wszystkim użytkownikom. Jeśli wybrałbyś np.Accounts in this organizational directory only
, tylko osoby z Twojej organizacji (zarejestrowanej w Azure) mogłyby z aplikacji korzystać - Kliknij przycisk Register
Tutaj się zatrzymamy i w następnych krokach utworzymy KeyVault.
Tworzenie ResourceGroup
W Azure właściwie wszystko jest zasobem. Zasoby można łączyć w grupy (Resource Groups). Bardzo dobrym nawykiem jest zamykanie w grupach konkretnych rozwiązań – projektów. Np. stworzenie Resource Group dla aplikacji A i zupełnie odrębnej Resource Group dla aplikacji B. Potraktuj ResourceGroup jak folder, w którym znajdują się wszystkie zasoby Azure potrzebne przy danym rozwiązaniu
Aby utworzyć Resource Group:
- zaloguj się na https://portal.azure.com
- z górnego menu wybierz subskrypcje
- z listy subskrypcji wybierz tę, na której chcesz pracować
- znajdź takie menu po lewej stronie, kliknij, a następnie kliknij Create na górze ekranu:
- Grupa musi mieć jakąś nazwę. Zwyczajowo nazwy zasobów tworzy się przez określenie rodzaju zasobu i jakąś nazwę, np:
rg-test
– rg od resource group, a test, no bo to moja grupa testowa na potrzeby tego artykułu:
- Region możesz zupełnie olać na tym etapie. Ja zawsze jednak wybieram Niemcy – bo są blisko Polski. Region mówi o tym, gdzie informacje na temat twojego zasobu będą trzymane fizycznie.
- Zatwierdź tworzenie grupy, klikając Review and create.
- Po utworzeniu grupy wejdź do niej.
Tworzenie KeyVault
Teraz, będąc w Resource Group, możesz utworzyć nowy zasób – KeyVault, czyli miejsce do trzymania sekretów.
- Z górnego menu wybierz Create aby utworzyć nowy zasób.
- W okienku wyszukiwania zacznij wpisywać „key vault”
- Powinieneś zobaczyć zasób KeyVault. Kliknij na niego aby go utworzyć.
- Na kolejnym ekranie wybierz plan subskrypcji. Przy KeyVault jest tylko jeden, więc po prostu wciśnij guzik Create.
- Teraz uzupełnij dane:
- Resource Group, do której ma być przypisany Twój Key Vault
- Nazwę KeyVault (np.
kv-moja-aplikacja
) - Region jaki chcesz
- Pricing Tier ustaw na Standard, zapewni ci to minimalne ceny
- Zatwierdź tworzenie KeyVault przyciskiem Review and create
Żeby Twoja apka mogła korzystać z tego KeyVault, musisz dać jej dostęp, ustawiając polityki dostępu.
- Przejdź do swojego KeyVaulta
- Z menu po lewej wybierz Access Policies, a następnie Add Access Policy
- Aby móc poprawnie odczytywać sekrety, musisz ustawić dostęp do Secret Permissions na Get i List
- Następnie dodaj principala, którym będzie twoja zarejestrowana wcześniej aplikacja (WebAPI zarejestrowane w AAD)
- Potwierdź tę konfigurację, wciskając przycisk Add
Super, teraz możesz zapisać jakiś sekret.
Tworzenie sekretów w KeyVault
Żeby umieścić jakiś sekret w KeyVault, wybierz z lewego menu Secrets, a następnie Generate/Import
Teraz możesz utworzyć swoje sekrety, do których nikt nie powinien mieć dostępu
Przy okazji, każdemu z sekretów możesz nadać daty, w których ma być aktywny. Ja umieściłem 5 sekretów bez żadnych dat. Jeśli się przyjrzysz obrazkowi wyżej, zobaczysz przy MailSettings dwie kreski. Te kreski oddzielają sekcję od wartości. To tak, jakbyś w pliku appsettings.json umieścił:
{
"BearerTokenSecret": "",
"MailSettings": {
"SmtpPassword": "",
"SmtpUser": "",
"SmptAddress": ""
},
"ConnectionString": ""
}
UWAGA!
To nie jest kurs obsługi KeyVault, ale muszę w tym momencie wspomnieć, że tutaj nie modyfikujesz swoich sekretów. Możesz dodać po prostu nową wersję. Jeśli klikniesz na jakiś sekret, w górnym menu zobaczysz opcję New Version – to pozwoli ci na dodanie nowej wartości tego sekretu.
Konfiguracja WebApi
Stwórz teraz WebAPI, które będzie pobierało dane z KeyVault. W tym celu, w Visual Studio utwórz standardowy projekt WebAPI.
Posłużymy się standardowym mechanizmem konfiguracji w .NetCore. Jeśli nie wiesz, jak działa konfiguracja w .NetCore, koniecznie przeczytaj ten artykuł.
Jak wspomniałem gdzieś na początku tego artykułu, możesz do KeyVault dobrać się na dwa sposoby. Z użyciem certyfikatu lub Managed Identity. Certyfikatem musisz się posłużyć, jeśli WebApi jest hostowane na serwerze zewnętrznym (poza Azure). Poniżej opisuję oba sposoby.
Łączenie z KeyVault za pomocą certyfikatu
Nikt ci nie zabroni użyć do tego najprostszego certyfikatu nawet self-signed. Ale dla bezpieczeństwa użyj tego, który masz na serwerze (to może być darmowy Let’s Encrypt). Przede wszystkim musisz uzyskać certyfikat w formacie CRT, CER lub PEM. Na szczęście możesz pobrać go ze swojej domeny.
Pokażę ci jak to zrobić skryptowo, żebyś mógł sobie ewentualnie ten proces zautomatyzować. Pamiętaj, że Let’s Encrypt jest ważny tylko 6 miesięcy. Po tym czasie zazwyczaj automatycznie się odnawia.
Pobieranie certyfikatu ze strony
Użyjemy aplikacji openssl, którą pewnie i tak masz już zainstalowaną. Jeśli nie, możesz pobrać ją stąd.
Aby pobrać certyfikat w formacie PEM, wykonaj poniższy skrypt:
echo "" | openssl s_client -host {host} -port 443 | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > {plik.pem}
- {host} – to oczywiście host, z którego pobierasz certyfikat, np. xmoney-app.pl
- {plik.pem} – to plik wyjściowy, do którego certyfikat zostanie zapisany, np.: certyfikat.pem
Żeby sprawdzić datę ważności certyfikatu, możesz posłużyć się takim poleceniem:
echo "" | openssl s_client -host {host} -port 443 | openssl x509 -inform pem -noout -dates
Tak pobrany certyfikat możesz wrzucić do Azure.
Dodawanie certyfikatu do aplikacji w Azure
Pamiętasz jak rejestrowaliśmy WebAPI w Azure AD? Teraz dodamy tam certyfikat.
Wejdź znów do AAD w taki sam sposób jak podczas rejestrowania aplikacji i przejdź do App Registrations. Wejdź do aplikacji, którą zarejestrowałeś na początku artykułu i z lewego menu wybierz Certificates & Secrets.
Wciśnij przycisk Upload certificate i wskaż plik certyfikat.pem
, który pobrałeś w poprzednim kroku.
Zobaczysz dodany certyfikat.
Teraz zostało już tylko uwierzytelnienie aplikacji WebAPI w KeyVault.
Uwierzytelnienie aplikacji za pomocą certyfikatu
Przede wszystkim musisz zainstalować dwie paczki NuGet:
Azure.Extensions.AspNetCore.Configuration.Secrets
- Azure.Identity
Teraz wystarczy to lekko skonfigurować. Dodaj taki kod podczas konfiguracji aplikacji WebAPI:
// using System.Linq;
// using System.Security.Cryptography.X509Certificates;
// using Azure.Extensions.AspNetCore.Configuration.Secrets;
// using Azure.Identity;
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((context, config) =>
{
if (context.HostingEnvironment.IsProduction())
{
var builtConfig = config.Build();
using var store = new X509Store(StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly);
var certs = store.Certificates.Find(
X509FindType.FindByThumbprint,
builtConfig["CertInfo:Thumbprint"], false);
config.AddAzureKeyVault(new Uri($"https://{builtConfig["Azure:KeyVaultName"]}.vault.azure.net/"),
new ClientCertificateCredential(builtConfig["Azure:AADDirectoryId"], builtConfig["Azure:ApplicationId"], certs.OfType<X509Certificate2>().Single()),
new KeyVaultSecretManager());
store.Close();
}
})
.ConfigureWebHostDefaults(webBuilder => webBuilder.UseStartup<Startup>());
Najpierw musisz pobrać certyfikat o podanym odcisku palca. Ja tutaj to robię z magazynu certyfikatów – w zależności od tego, gdzie certyfikat masz zainstalowany, użyjesz StoreLocation.CurrentUser
lub StoreLocation.LocalMachine
.
UWAGA! Na niektórych serwerach współdzielonych możesz mieć problem do dobrania się do certyfikatu z poziomu aplikacji. Być może aplikacja będzie wymagała wyższych uprawnień. Jeśli tak będzie, porozmawiaj z supportem swojego hostingu.
Następnie dodajesz swojego KeyVaulta do konfiguracji .NetCore i to tyle. Od tego momentu wszystkie sekrety możesz pobierać używając IConfiguration
.
Oczywiście ja w tym kodzie nie podaję żadnych danych na sztywno – one są brane z konfiguracji, co zapewne widzisz. Czyli z pliku appsettings.json, dlatego w linijce 12 buduję wstępną konfigurację. Rzeczy, które się tu znajdują nie są sekretami i dostęp osób trzecich do tych informacji niczym nie grozi. Przykładowy appsettings.json może wyglądać tak:
{
"Azure": {
"KeyVaultName": "{nazwa twojego KeyVault}",
"AADDirectoryId": "{Id twojego tenanta}",
"ApplicationId": "{Id twojej aplikacji}"
},
"CertInfo": {
"Thumbprint": "{odcisk palca certyfikatu}"
}
}
To teraz, skąd wziąć te dane? Nazwa KeyVault to wiadomo – taką nazwę podałeś podczas tworzenia KeyVault.
Jeśli chodzi o Id tenanta i Id aplikacji… Wejdź znów na Azure do Azure Active Directory, następnie wejdź w AppRegistrations i aplikację, którą rejestrowałeś:
Application (client) ID to Id twojej aplikacji. Natomiast Directory (tenant) Id to Id twojego tenanta (tenant czyli subskrybent).
A skąd wziąć odcisk palca certyfikatu? Z rejestracji aplikacji na AAD, tam gdzie dodawałeś certyfikat. To jest kolumna Thumbprint.
Te literki i cyferki to jest właśnie odcisk palca Twojego certyfikatu.
Łączenie z KeyVault z pomocą Managed Identity
Jeśli hostujesz swoją aplikację na Azure, możesz do KeyVault dobrać się za pomocą ManagedIdentity. To jest druga opcja. Tylko zaznaczam – wymaga hostowania Twojego WebApi na Azure.
Managed Identity pozwala aplikacji na łączenie się z innymi zasobami Azure. Nie tylko KeyVault.
Dodanie Managed Identity
Skoro jesteś w tym miejscu, zakładam że masz już utworzony AppService na Azure. Jeśli nie, to utwórz sobie w swojej resource group zasób o nazwe Web App (to utworzy tzw. AppService).
Następnie na poziomie tej aplikacji (App Service) kliknij w menu Identity po lewej stronie.
Następnie zmień Status na On i zapisz to. Właśnie dodałeś Managed Identity systemowe w swojej aplikacji. Teraz tylko zwróć uwagę na Object (principal) ID. Skopiuj ten identyfikator.
Kilka akapitów wyżej dodawałeś Access Policy do swojego KeyVault. Teraz dodaj nową właśnie dla tej aplikacji. Zamiast nazwy wklej po prostu ten identyfikator.
Uwierzytelnienie aplikacji w KeyVault za pomocą ManagedIdentity
Pokrótce, cały flow polega na wysłaniu żądania do Azure w celu otrzymania tokenu. Otrzymany token przekazujemy dalej w kolejnych żądaniach do chronionych zasobów. Oczywiście w .NET możemy to ogarnąć gotową biblioteką i tak też zrobimy.
Najpierw pobierz Nuget:
- Azure.Identity
Teraz na scenę wchodzi klasa DefaultAzureCredential
. Jest to cudo, które w środowisku deweloperskim uwierzytelnia cię w Azure za pomocą danych, które masz wklepane w zmienne środowiskowe. Jeśli apka znajduje się już na Azure, wtedy jest uwierzytelniana kontem Azurowym.
W środowisku deweloperskim musisz się zalogować do Azure, żeby sobie wszystko poustawiać. W Visual Studio wejdź do Tools -> Options
i w oknie opcji znajdź Azure Service Authentication
. Upewnij się, że jestem tam zalogowany na odpowiednim koncie:
To spowoduje, że DefaultAzureCredential
pobierze odpowiednie dane.
Teraz już tylko zostało połączenie apki z KeyVaultem. Zrobisz to podczas jej konfiguracji dokładając taki kod:
// using Azure.Identity;
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((context, config) =>
{
if (context.HostingEnvironment.IsProduction())
{
var builtConfig = config.Build();
config.AddAzureKeyVault(new Uri($"https://{builtConfig["KeyVaultName"]}.vault.azure.net/"),
new DefaultAzureCredentials());
store.Close();
}
})
.ConfigureWebHostDefaults(webBuilder => webBuilder.UseStartup<Startup>());
Kod jest banalny. Najpierw wstępnie budujemy konfigurację, żeby móc odczytać dane z appsettings. Następnie dodajemy konfigurację KeyVault, przekazując nazwę twojego KeyVaulta. Nazwa ta jest brana z appsettings, żeby nie trzymać jej na sztywno w kodzie. Ten fragment w appsettings może wyglądać tak:
{
"KeyVaultName": "{nazwa twojego KeyVault}"
}
Od tego momentu możesz odczytywać sekrety normalnie przez IConfiguration
jak gdyby były częścią pliku appsettings.json. I robisz to bezpiecznie, nie pokazując światu żadnych tajemnic.
Koniec!
Uff, to koniec. Wiem, że dużo pojawiło się zagadnień i nie wszystko może być od razu jasne. Jeśli masz jakieś pytania lub wątpliwości, koniecznie daj znać w komentarzu. Jeśli znalazłeś błąd w artykule, również się podziel 🙂
Również napisz, jeśli znasz bezpośredni bezpieczny sposób na odczytanie danych z KeyVault przez aplikację desktopową.