Ostatnio wpadłem na problem. Przechowywanie credentiali w Azure Function. W aplikacji ASP.NET Core można użyć do tego secrets.json
. A w Azure Functions? Okazuje się, że istnieje usługa Azure stworzona do tego typu zadań: Key Vault. Jednak połączenie Azure Key Vault z Azure Functions nie jest takie oczywiste (przynajmniej dla mnie) a wiele tutoriali, które znalazłem na ten temat nie działa. Postanowiłem opisać więc jak wykonać takie połączenie w działający sposób. Przejdźmy zatem do rzeczy.
Na początku musimy utworzyć sobie Azure Function. Następnie musimy wstępnie ją skonfigurować do korzystania z Azure Key Vault. W funkcji włączamy Azure Managed Service Identity. Już pokazuje jak to zrobić.
MSI w funkcji
Wybieramy funkcje którą chcemy skonfigurować. Przechodzimy na kartę Platform features
i wybieramy Managed service identity
:
W otwartym oknie przestawiamy opcję Register with AAD
na On i zapisujemy zmiany:
Teraz możemy już przejść do konfiguracji Key Vault.
Azure Key Vault
Po pierwsze musimy utworzyć Azure Key Vault. Następnie, po wejściu do utworzonego przed chwilą Key Vault, z listy po lewej stronie wybieramy Access policies
i klikamy Add new
:
Wynikiem tego działania powinno być takie okno:
Następnie klikamy na pole Select principal
i wybieramy funkcje która utworzyliśmy na początku:
Z dropdowna Secret permissions
wybieramy opcję Get:
I klikamy przycisk ok, aby potwierdzić dodawanie. Zostaniemy przekierowani do strony naszego Key Vault. Teraz musimy zapisać ustawienia przyciskiem Save
:
Teraz przechodzimy do zakładki Secrets
i naciskamy Generate/Import
:
Po wyświetleniu okna wypełniamy pola Name
– nazwa przechowywanej tajnej wartości (hasło, token itp.) i Value
– sama wartość hasła, tokena itp.:
Gdy wypełnimy te pola klikamy przycisk Create
.
Ostatnią rzeczą jaką musimy zrobić w Key Vault to skopiowanie adresu pod którym tenże rezyduje. Zrobimy to w karcie Overview
w polu DNS Name
:
Konfiguracja Azure Function
Aby skonfigurować naszą funkcję do działania z Key Vault w zasadzie nie musimy robić nic. Wystarczy tylko użyć odpowiedniego kodu. Natomiast to co przedstawię za chwilkę jest dobra praktyką. Przechodzimy więc do naszej funkcji i wybieramy link Application settings
:
Dzięki temu otwarta zostanie karta w Azure Portal o tej samej nazwie. Zjeżdżamy trochę niżej aż dotrzemy do Application settings
Klikamy opcję + Add new setting
. Jako nazwę wpisujemy np. KeyVaultUri. Jako wartość wklejamy wcześniej skopiowany adres Key Vault. Rezultat powinien wyglądać następująco:
Wjeżdżamy na górę i klikamy przycisk Save
.
Dostęp do Key Vault z kodu
Teraz zostało już tylko pobrać w odpowiedni sposób wpis z secretem z Key Vault z poziomu kodu funkcji. Przedstawię, jak wygląda taki kod w C#.
To co ważne w kontekście kodu to metoda GetSecretAsync(String keyVaultUrl, String secretName)
(link do opisu na MSDNie). Podajemy w niej jako pierwszy argument URL do Key Vaulta. Natomiast jako drugi argument podajemy nazwę naszego secreta który chcemy uzyskać. W naszym przypadku URL do Key Vaulta został zapisany w App Settings (zmienna środowiskowa). Jej pobranie wygląda tak:
Environment.GetEnvironmentVariable("KeyVaultUri")
Gdzie KeyVaultUri
to nazwa zmiennej środowiskowej w App Settings którą zapisaliśmy w poprzednim „rozdziale”. Tak więc pobranie faktycznego hasła/tokenu/co tam trzymamy w secret w Key Vault wygląda następująca:
Cały kod służący do pobrania wartości z wpisu typu Credential w Key Vault wyglada tak:
private static HttpClient _client = new HttpClient(); var azureServiceTokenProvider = new AzureServiceTokenProvider(); var kvClient = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(azureServiceTokenProvider.KeyVaultTokenCallback), _client); //poniższa linijka pobiera nasze hasło które zapisaliśmy w Key Vault jako secret string password = (await kvClient.GetSecretAsync(Environment.GetEnvironmentVariable("KeyVaultUri"), "secret-name")).Value;
Tutaj warto też znznaczyć, że Azure Functions nie zawsze startowane są od zera. Co mam na myśli to to, że niektóre obiekty deklarowac jako static. Tak jak zrobiłem to z HttpClient-em. Warto zastosować też ta strategie przy pobieraniu Credentiali. Czyli obiekt przechowujący/potrzebujący tajne dane też utworzyć jako static. I dopiero, gdy jest on nullem (funkcja startuje od zera) pobierać wartości z Azure Key Vault. Spowoduje to mniejszy czas wywołania funkcji oraz mniejsze koszta związane z odczytem wartości z Azure Key Vault.
Jeśli pobieram hasło, aby utworzyć EmailCredentials zrobię to tak:
private static NetworkCredential _emailCredentials; private static HttpClient _client = new HttpClient(); if (_emailCredentials == null) { var azureServiceTokenProvider = new AzureServiceTokenProvider(); var kvClient = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(azureServiceTokenProvider.KeyVaultTokenCallback), _client); var emailPassword = (await kvClient.GetSecretAsync(Environment.GetEnvironmentVariable("KeyVaultUri"), "secret-name")).Value; _emailCredentials = new NetworkCredential("[email protected]", emailPassword); }
Jest to całkiem proste, aczkolwiek dojście do poprawnie działającego kodu zajęło mi trochę czasu.