Access Azure Key Vault Secrets using Csharp in Azure Function App [duplicate] - c#

This question already has answers here:
How to get all secrets in one call Azure key vault
(5 answers)
Closed 1 year ago.
Is it possible that we can access all the secrets of an Azure Key Vault in AzureFunctionApp using Csharp.

I tried to reproduce the issue:
Created a Function App in Azure Portal and an HTTP Trigger Function inside the Function through the portal itself.
Created a KeyVault resource and Secret through the Azure Portal.
Copy your KeyVault SecretIdentifier in any text editor.
In the same text editor, copy this setting:
#Microsoft.KeyVault(SecretUri=https://myvault.vault.azure.net/secrets/mysecret/)
Replace the SecretUri with your copied KeyVault Secret Identifier value from the KeyVault resource.
In the Function App - Configuration (Settings in left index pane), Add secret identifier setting in Application Settings like below:
Go to Your Function App > Identity (under the Settings Pane) > Switch System Assigned Managed Identity Status to On and Save.
Go to Your KeyVault Resource > Access Policies (under the Settings Pane) > Add New Access Policy:
Configure from template: Key & Secret Management
Key Permissions: Select/Deselect (Optional)
Secret Permissions: Select the permissions required like Get...
Certificate Permissions: Select/Deselect (Optional)
Select Principal: Authorize your function app to access this key vault
Go to your Function App > Functions (Select your Function) > Click on Code + Test Option > Add this two lines in the run.csx file.
var secretValue = Environment.GetEnvironmentVariable("kvsecret",EnvironmentVariableTarget.Process);
log.LogInformation($"SecretValue from kvsecret in krishkeyvault02 : {secretValue}");
Here kvsecret is your key vault secret name.
Click on Save and Test/Run where I provided the body name parameter as Krishna.
Test Output:
Run.csx code:
#r "Newtonsoft.Json"
using System.Net;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Primitives;
using Newtonsoft.Json;
public static async Task<IActionResult> Run(HttpRequest req, ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");
var secretValue = Environment.GetEnvironmentVariable("kvsecret", EnvironmentVariableTarget.Process);
log.LogInformation($"SecretValue from kvsecret in krishkeyvault02 : {secretValue}");
string name = req.Query["name"];
string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
dynamic data = JsonConvert.DeserializeObject(requestBody);
name = name ?? data?.name;
string responseMessage = string.IsNullOrEmpty(name)
? "This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response."
: $"Hello, {name}. This HTTP triggered function executed successfully.";
return new OkObjectResult(responseMessage);
}
Note:
Looking at the documentation, the KeyVaultClient Class doesn't contain a method to get all secrets including their values. The GetSecrets method 'List secrets in a specified key vault.' and returns a list with items of type SecretItem, which doesn't contain the value but only contains secret metadata.
This is in line with the Key Vault REST API, where there's a GetSecrets that returns... a list of SecretItems.
I guess, if you want all values of all secrets, you have to iterate the list and get everyone explicitly.
There are few code snippets available to retrieve secret values provided by other communities, please refer this SO Thread

Related

Firebase: Authenticating from C# .NET Core application

I am attempting to connect a local .NET Core project to a Firebase project. I am using the Google documentation at https://cloud.google.com/dotnet/docs/reference/Google.Cloud.Firestore/latest:
private async Task<Dictionary<string, object>> GetData()
{
string projectId = "My-Project";
FirestoreDb db = FirestoreDb.Create(projectId);
Dictionary<string, object> documentDictionary = new Dictionary<string, object>();
...
}
When it hits the call to create an instance of the project (FirestoreDb.Create(projectId)), I receive this error:
Error: System.InvalidOperationException: The Application Default Credentials are not available. They are available if running in Google Compute Engine. Otherwise, the environment variable GOOGLE_APPLICATION_CREDENTIALS must be defined pointing to a file defining the credentials. See https://developers.google.com/accounts/docs/application-default-credentials for more information.
at Google.Apis.Auth.OAuth2.DefaultCredentialProvider.CreateDefaultCredentialAsync()
I have exported the service account from Firebase into a JSON file, generating a private key and saving it locally:
I have added the path of the key file to this JSON file in an environment variable:
However, I continue to get the error.
There is one way I have been able to authenticate, and that was using the example code at https://cloud.google.com/docs/authentication/production:
public object AuthExplicit(string projectId, string jsonPath) {
var credential = GoogleCredential.FromFile(jsonPath);
var storage = StorageClient.Create(credential);
var buckets = storage.ListBuckets(projectId);
foreach (var bucket in buckets)
{
Console.WriteLine(bucket.Name);
}
return null;
}
However, I don't know how (or if it's possible) to link that authentication code with Firebase in order to retrieve my data.
Is there any guidance you can provide so I can properly authenticate and pull my data?
The missing step was calling the FirebaseApp.Create() method, as described in the Firebase authentication documentation.
I modified my application to add that method before the call to FirestoreDb.Create(), and that did it. Here is the modified code:
private async Task<Dictionary<string, object>> GetData() {
string projectId = "My-Project";
FirebaseApp.Create(new AppOptions()
{
Credential = GoogleCredential.GetApplicationDefault(),
});
FirestoreDb db = FirestoreDb.Create(projectId);
Dictionary<string, object> dd = new Dictionary<string, object>();
...
}
This link provides a reason why this error is being caused.
I – If GOOGLE_APPLICATION_CREDENTIALS is SET and it uses the service
account file path using the value associated with the above
Environment variable GOOGLE_APPLICATION_CREDENTIALS
II – If GOOGLE_APPLICATION_CREDENTIALS is not SET, then the following preferences are set:
ADC uses a service account file that is running the code.
Otherwise, if a service account also does exist, then ADC uses the default service account that Compute Engine, Google Kubernetes Engine, App Engine, and Cloud Functions provide.
Try to debug the application locally by doing the following:
If you already have a service account created, then it applies to an application instead of an individual user. You need to authenticate a service account while accessing your IAP-secured resources.
Please make sure to set Environment Variable GOOGLE_APPLICATION_CREDENTIALS with the secured key JSON file path.
Example
C#:
Environment.SetEnvironmentVariable("GOOGLE_APPLICATION_CREDENTIALS", "secured-service-account.json");

Why doesn’t the "https://login.microsoftonline.com/common" AAD endpoint work, while the "https://login.microsoftonline.com/[tenant ID]" does?

I’m developing a UWP application that calls an API. The API is made of an Azure Function triggered by HTTP requests. I want the Azure Function to be secured through Azure Active Directory. To do so, I created two app registrations in AAD, one for the UWP and one for the API. Both support accounts in any organizational directory (Any Azure AD directory - Multitenant) and personal Microsoft accounts (e.g., Skype, Xbox). The API app registration provides scope, and the UWP app registration uses that scope. The code I use on my UWP is:
var HttpClient _httpClient = new HttpClient();
const string clientId = "[UWP app registration’s client ID]";
const string authority = "https://login.microsoftonline.com/[Tenant ID of the UWP app registration]";
string[] scopes = { "api://[API app registration’s client ID]/[scope]" };
var app = PublicClientApplicationBuilder
.Create(clientId)
.WithAuthority(authority)
.WithRedirectUri("https://login.microsoftonline.com/common/oauth2/nativeclient")
.Build();
AuthenticationResult result;
var accounts = await app.GetAccountsAsync();
try {
result = await app.AcquireTokenSilent(scopes, accounts.FirstOrDefault()).ExecuteAsync();
}
catch (MsalUiRequiredException) {
try {
result = await app.AcquireTokenInteractive(scopes).ExecuteAsync();
}
catch (Exception exception) {
Console.WriteLine(exception);
throw;
}
}
if (result == null) return;
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
var response = _httpClient.GetAsync("[API URL]").Result;
This code works, but if I replace the authority with https://login.microsoftonline.com/common (as specified here), being my app registrations multi-tenant, I get a 401 response when calling the API _httpClient.GetAsync("[API URL]").Result. The docs say the code must be updated somehow when using the /common endpoint, but I don’t understand how I should edit it. I also tried to follow these tips, but without success, while these seem not to be related to my case since I’m not building an IWA. If I run the working version of the code, result is populated with an object whose TenantId property gets the right value of the tenant that owns the app registrations while using the not-working version of the code, result is populated with an object whose TenantId property gets a value I don’t know where it’s coming from.
Can anyone help me, please?
Here's my understanding of AAD multitenancy flow :
The common authority can't be used to get a token. It's used as a common endpoint to get the templated server metadata :
v1 : https://login.microsoftonline.com/common/.well-known/openid-configuration
v2 : https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration
A token should be requested from the issuer where the client is defined.
But the common authority can be used in a multitenant API (eg your Azure Functions API) to verify that a client has a valid AAD token. From the documentation :
Because the /common endpoint doesn’t correspond to a tenant and isn’t an issuer, when you examine the issuer value in the metadata for /common it has a templated URL instead of an actual value : https://sts.windows.net/{tenantid}/
Therefore, a multi-tenant application can’t validate tokens just by matching the issuer value in the metadata with the issuer value in the token. A multi-tenant application needs logic to decide which issuer values are valid and which are not based on the tenant ID portion of the issuer value.

Accessing secret value from Identifier

How do I retrieve the "secret value" in my Azure Function without using the properties of the Azure function?
Note: The value for the secret URL can be different every time the function is called, so it needs to resolve while running the application and not using configuration or function properties.
Similar problem but not solved using IBinder (my preferred solution):
Can Azure Key Vault be used with Functions to store the connection string for queue triggers?
How to map Azure Functions secrets from Key Vault automatically
[FunctionName("functionName")]
public async static void Run(
[QueueTrigger("queueName", Connection = "StorageConnectionAppSetting")],
IBinder binder,
ILogger log)
{
// TODO: how to resolve access from IBinder
binder.
}
background for the problem:
I have an application that has stored key/value pair within the Azure KeyVault in secrets
while storing the data, I keep the Identifier for later retrieval:
"ClientSecretUri":"https://keyvault.vault.azure.net:443/secrets/1-ff6b03fc-12e8-427f-fa18-08d845672373/78c0211ceb5140a8990dec450eef1d23"
my code for storing the value is:
var kvc = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(
AzureAccessToken.GetAccessToken(APP_CLIENT_ID, APP_CLIENT_SECRET)
));
var secretBundle = await kvc.SetSecretAsync(KEYVAULT_BASE_URI, keyToStoreInValut, clientSecret);
var ClientSecretUri = secretBundle.SecretIdentifier.Identifier;
As the code you provided, you can get the secret Identifier with ClientSecretUri.
Then you could use ClientSecretUri to access the latest secret value.

azure data lake authorization

I am new to Azure Data Lake Analytics and am converting a C# batch job to use service to service authentication before submitting stored procedures to Azure Data Lake Analytics.
public void AuthenticateADLUser()
{
//Connect to ADL
// Service principal / appplication authentication with client secret / key
SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());
cTokenCreds = ApplicationTokenProvider.LoginSilentAsync(strDomain, strWebApp_clientId, strClientSecret).Result;
SetupClients(cTokenCreds, strSubscriptionID);
}
public static void SetupClients(ServiceClientCredentials tokenCreds, string subscriptionId)
{
_adlaClient = new DataLakeAnalyticsAccountManagementClient(tokenCreds);
_adlaClient.SubscriptionId = subscriptionId;
_adlaJobClient = new DataLakeAnalyticsJobManagementClient(tokenCreds);
_adlsFileSystemClient = new DataLakeStoreFileSystemManagementClient(tokenCreds);
}
Even though I have given it the correct ClientId the error comes back with a different ClientID in the error when I execute the following code:
var jobInfo = _adlaJobClient.Job.Create(_adlsAccountName, jobId, parameters);.
The error message is:
The client 'e83bb777-f3af-4526-ae34-f5461a5fde1c' with object id 'e83bb777-f3af-4526-ae34-f5461a5fde1c' does not have authorization to perform action 'Microsoft.Authorization/permissions/read' over scope '/subscriptions/a0fb08ca-a074-489c-bed0-....
Why is the ClientID different than the one I used in the code?
Is this a code issue or a permissions issue? I assume that it is code since the ClientID is not an authorized one that I created.
note: The SubscriptionId is correct.
I assumed you created an Azure Active Directory App and are you the client and domain IDs of this app. If not, you'll need that... If you do have that, then can you check if the App has permissions over your Data Lake Store: https://learn.microsoft.com/en-us/azure/data-lake-store/data-lake-store-authenticate-using-active-directory
Had exactly same symptoms. WebApp was created in AAD in portal originally to access Azure Data Lake Store and same code-snippet worked perfectly. When I decided to re-use same WebApp (clientid/secret) it failed with same error, even though I have given reader/contributor roles on sub/RG/ADLA to the App.
I think the reason is that WebApp underneath has a "service principal" object (thus error msg shows different object id) and ADLA uses it for some reason. Mine didn't have credentials set - empty result:
Get-AzureRmADSpCredential -objectid <object_id_from_error_msg>
Added new password as described here
New-AzureRmADSpCredential -objectid <object_id_from_error_msg> -password $password
Used the pwd as secret in LoginSilentAsync, clientId was left as before - WebApp clientId (not the principal object id shown in the error)
I wasn't able to find this principal info in portal, only PS.

Azure key vault: access denied

I have the following code for obtaining a secret from the Azure key vault:
public static async Task<string> GetToken(string authority, string resource, string scope)
{
var authContext = new AuthenticationContext(authority);
ClientCredential clientCred = new ClientCredential(...); //app id, app secret
AuthenticationResult result = await authContext.AcquireTokenAsync(resource, clientCred);
if (result == null)
throw new InvalidOperationException("Failed to obtain the JWT token");
return result.AccessToken;
}
public static string GetSecret(string secretName)
{
KeyVaultClient keyVaultClient = new KeyVaultClient(GetToken);
try
{
return keyVaultClient.GetSecretAsync("my-key-vault-url", secretName).Result.Value;
}
catch(Exception ex)
{
return "Error";
}
}
The error I am getting is "access denied", which (I think) means that the id, secret and the vault's url are fine. However, I don't know what I can do differently to fix this error, is there maybe a setting in the Azure portal which is preventing me from reading a secret?
To fix access denied you need to configure Active Directory permissions. Grant access to KeyVault.
1. Using PowerShell
Run next command:
Set-AzureRmKeyVaultAccessPolicy -VaultName 'XXXXXXX' -ServicePrincipalName XXXXX -PermissionsToKeys decrypt,sign,get,unwrapKey
2. Using the Azure portal
Open Key Vaults
Select Access Policies from the Key Vault resource blade
Click the [+ Add Access Policy] button at the top of the blade
Click Select Principal to select the application you created earlier
From the Key permissions drop down, select "Decrypt", "Sign", "Get", "UnwrapKey" permissions
Save changes
Authorize the application to use the key or secret
The question did specify using the Azure Portal, I've documented creating a service principal for Key Vault access here.
Specifically from Step 2:
Open the Key Vault in the Azure Portal and select the Access policies blade under Settings. Click Add New and click on Select principal - you'll have to enter the full name of the registered app you created in the previous step in the search box before it'll show up, at which point you'll be able to select it.
You can either select an appropriate template from the top dropdown or choose Key, Secret or Certificate permissions manually. Don't worry about Authorized application at this stage.
IMPORTANT: pressing the OK button will add your new policy to the list, but it will not be saved! Be sure to click Save before continuing.
What is happening - your service principal doesn't have permissions to perform said operation. Take a look at this thread.
How do I fix an "Operation 'set' not allowed" error when creating an Azure KeyVault secret programmatically?
Access Key Vault in .Net code
Azure Setting:-
App Service-
1-Enable-MSI(Managed service identity)-ON
Key Vault:
1-Open Key Vault
2-Select Access Policies from the Key Vault resource blade
3- Click the [+ Add new] button at the top of the blade
4-Click Select Principal to select the application(App Service) you created earlier
.Net Code:-
Code to Access key vault secrets in .Net Code
var azureServiceTokenProvider = new AzureServiceTokenProvider();
var keyVaultClient = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(azureServiceTokenProvider.KeyVaultTokenCallback));
var secret = keyVaultClient.GetSecretAsync("https://test.vault.azure.net/", "clientid").Result.Value;
If you want to authorize that same application to read secrets in your vault, run the following:
Set-AzureRmKeyVaultAccessPolicy -VaultName 'yourKeyVaultName' -ServicePrincipalName ClientId -PermissionsToSecrets Get
When you register application in Azure ClientId is generated.
I had the same problem and I added my IP address under KeyVault firewall.

Categories

Resources