I'm working on a C# desktop application (a plugin for a 3D modelling software) and from this I need to access info from a GraphQL endpoint. In order to access the endpoint I need to provide a secret key. So what I've done is to store that key on Azure Key Vault.
And this is the code I'm using to get the key from Azure Key Vault:
var clientProd = new SecretClient(new Uri("my-azure-keyvault-url"),
new ClientSecretCredential(
"tenantId",
"clientId",
"clientSecret"));
Response<KeyVaultSecret> secret = await clientProd.GetSecretAsync("cbe-graphQL-prod");
But now I'm finding myself in this sort of vicious loop, where I'm not sure where that clientSecret should come from so it's not pushed to version control.
I'm going to deploy this application to multiple users in my company, so I wouldn't want to create an environment variable on everyone's machine (not even with a group policy).
What would be the best way to do this?
Thanks!
If you are hosting your application in Azure, you can use System Assigned Managed Identity to access the key vault without any credentials (client id, client secret)
If your application is hosted on premises, you can limit the access of the app registration (service principal) to the key vault in which secret is stored.
Then you can either store the client secret as environment variable or hard code the client secret
Related
I have this web app which access a keyvault stored in Azure cloud.
To access this KeyVault I use the IConfigurationBuilder Extension
configuration.AddAzureKeyVault(new Uri(KeyvaultUri), new DefaultAzureCredential(true));
I have created an managed identity for all the user who need access to this, meaning they should be able to run the application and have access to the keyvault once they are logged in via SSO, which they currently are forced to do everytime they start the application due to new DefaultAzureCredential(true) What I don't understand is why it everytime need to be requested everytime, and not store the credentials somewhere after it has been entered once, and use that stored credential, can I somehow locally store the required credentials after the initial login?
It is sort of inconvenient to always login when one start their application, and debugging application becomes a bit lengthy with the required login.
Is somehow possible to let the login happen in the background - or somehow store the credentials after first login?
I feel a bit this is getting off tracked - the solution I am seeking should be applicable for those running the solution via a terminal, outside of visual studio.
Such as frontend developers - who just need a backend to make reqeuest to a nothing else.
It has no sense to cache the token since it is used only once at startup, what you are looking for is to exclude in your credentials all the wrong ways you are trying to grab the token to connect to your AKV except the one you are really using.
To configure it correcly and do not wait 15 seconds in your startup you should configure DefaultAzureCredentials this way:
DefaultAzureCredential credentials = new DefaultAzureCredential(new DefaultAzureCredentialOptions
{
ExcludeEnvironmentCredential = true,
ExcludeInteractiveBrowserCredential = true,
ExcludeAzurePowerShellCredential = true,
ExcludeSharedTokenCacheCredential = true,
ExcludeVisualStudioCodeCredential = true,
ExcludeVisualStudioCredential = true,
ExcludeAzureCliCredential = true,
ExcludeManagedIdentityCredential = false,
});
Exclude all posibilities to grab the token except the one you are using, in this case "Managed Identity" or in other cases AzureCliCredentials.
Regards.
Can you share a small full code example ?
What about using DefaultAzureCredentialOptions.
Like:
.ConfigureAppConfiguration((context, config) =>
{
var appSettings = config.Build();
var credentialOptions = new DefaultAzureCredentialOptions();
var credential = new DefaultAzureCredential(credentialOptions);
config.AddAzureKeyVault(new Uri(appSettings["Url:KeyVault"]), credential);
})
I assume you've got different Azure subs for non-prod and prod (that contain a non-prod and prod key vault instances).
In the non-prod key vault instance, create a new role assignment with the required members i.e. the developer AAD accounts.
Given you're using DefaultAzureCredential, these members will be able to leverage Azure service authentication in Visual Studio or Visual Studio Code (or EnvironmentCredential) as DefaultAzureCredential will cycle through these various credential types including VisualStudioCredential and VisualStudioCodeCredential (and EnvironmentCredential) respectively.
Developers can authenticate in their IDE e.g. in Visual Studio by going to Tools > Options > Azure Service Authentication where they can authenticate using their Azure credentials.
Assuming their AAD accounts have been granted access to the (non-prod) key vault instance, they will be able to get access.
The deployed application - presumably running in Azure - would use a different credential type e.g. ManagedIdentityCredential or EnvironmentCredential. Given these are also handled by the DefaultAzureCredential, no code changes would be required for this to work for the deployed instance of your app.
The only difference with the prod key vault instance is that you probably wouldn't create role assignments for the developer accounts.
the solution I am seeking should be applicable for those running the solution via a terminal, outside of visual studio. Such as frontend developers - who just need a backend to make reqeuest to a nothing else.
For these type of users that just want to run the app from the terminal, they could set some environment variables that will get picked up by the EnvironmentCredential (which is another one of the credential types included in the DefaultAzureCredential) e.g. if they're running the app in docker they could specify AZURE_USERNAME and AZURE_PASSWORD environment vars (or alternatively AZURE_CLIENT_ID and AZURE_CLIENT_SECRET for more of a 'machine' context) e.g. docker run -e AZURE_USERNAME=username -e AZURE_PASSWORD=password ...
Based upon your question, I am assuming the following:
The web application has a managed identity and has
permissions to your key vault.
The users are logging in as themselves
in the front end of the web application.
The new DefaultAzureCredential(true) just grabs the users current credentials. This will be the front end user. These are cached automatically based upon your organization's security policy. I assume this is working correctly.
The login frequency is out of your control as a developer. The issue you are having is in the organizational settings in Azure Active Directory. Your sign in frequency may be set to one of these:
Require reauthentication every time
Sign-in frequency control every time risky user
To fix your issue, set the sign-in frequency to be less than that and you should be good. (I don't have access to this or I would post better pictures)
Here is the link to the full article on how to do this:
Configure authentication session management with Conditional Access
This was what I was looking for
https://github.com/Azure/azure-sdk-for-net/issues/23896
A silent authentication step that stores the credentials first time it is entered into a cache file and then reuses it when rerunning the application the second time without prompting the user to enter credentials.
Thus ensuring credentials arent stored in git, and cache file stored locally on each developers local machine.
I'm fairly confident I set everything up right for a multi-tenant app.
In Tenant A:
I created an Azure function and enabled a system managed identity. I granted the Managed Identity permissions to the Graph API. I confirmed my API can obtain an access token for the Managed Identity and access the Graph API with it. I added Azure AD authentication to the app and created an App Registration for the app. I configured the necessary Graph API permissions in the API Permissions settings of the app registration. I also enabled the various options to enable multi-tenant access.
In Tenant B:
I accessed the special URL to start the admin consent process for the app in Tenant A. I confirmed that I was prompted to consent to the permissions that I specified in the app registration in Tenant A. I can see that a couple new enterprise application entries were created in Tenant B for the app in Tenant A.
So far so good. However, no matter what I do, I cannot obtain a token to access the graph API in the context of Tenant B. It ALWAYS gives me a token for the managed identity in Tenant A, and accesses the info in Tenant A.
The code the retrieves an access token using the managed identity is here:
var credential = new DefaultAzureCredential(new DefaultAzureCredentialOptions { AdditionallyAllowedTenants = { "*" }, TenantId = "<Tenant B ID>" });
var token = credential.GetToken(
new Azure.Core.TokenRequestContext(
new[] { "https://graph.microsoft.com/.default" }, null, null, "<Tenant B ID"));
var accessToken = token.Token;
Now, I have tried all sorts of different combinations here. But, from everything I read, I am supposed to be able to specify the Tenant ID of another tenant in these methods and obtain an access token for that specific tenant. However, even when I specify Tenant B's ID, I always get a token back for the managed identity in Tenant A. If I use the token, of course, I end up accessing all of Tenant A's information.
Ironically, if I remove the DefaultAzureCredentialOptions and include the tenantID in the GetToken request I get an error telling me that I'm trying to obtain a token for a different tenant (than my own) and that I need to add the AdditionallyAllowedTenants option. It clears the error up when I add that, but then it doesn't obtain a token for the other tenant.
Perhaps I am still approaching this wrong. I want to host a multi-tenant Azure Function that runs in the context of the Tenant where the request originated from to access information within that tenant using a managed identity that exists within that tenant. I can obtain the Tenant ID context from the claims sent to the Azure function during authentication, but no matter how I try to specify that Tenant ID in my code, it will not get a token for that Tenant.
UPDATE and SOLUTION:
Thanks to Philippe's great write up, I did finally get this. I had some confusion around the mechanisms at play here and want to add this note for clarity on how I solved it.
If you've followed most of the documents you have an Azure Function app in your "host" tenant A, with an associated app registration in tenant A. The app registration has certain API permissions configured, and you have consented to this app and permissions in Tenant B.
To access the resources in Tenant B, you need to create a secret key or certificate on the app registration in Tenant A. You then need to authenticate using the Client ID, and secret key/certificate of the app registration in Tenant A, but you will request a token for Tenant B. Here it is in code using a certificate for authentication:
var appToken = new ClientCertificateCredential(tenantID, appID, appCert, new ClientCertificateCredentialOptions { AdditionallyAllowedTenants = { "*" } });
var graphServiceClient = new GraphServiceClient(appToken);
Here, we assign the following to the variables:
tenantID = Tenant B's ID
appID = Tenant A's App registration client ID
appCert = Tenant A's App registration cert
We have to include the AdditionallyAllowedTenants config parameter, and the * authorizes a token from any tenant. We then take that credential and use it to build a new GraphServiceClient that is connected to Tenant B.
You've created two separate and independent identities for your service:
The managed identity: You don't need to deal with credentials, but this identity can only be used within the same tenant. You can't use your managed identity to directly access another tenant, and you can't (currently) use your managed identity to authenticate as your app registration.
The app registration: This identity can be used to access data in another tenant, but (currently) you do need to deal with credentials.
The DefaultAzureCredential will attempt different ways of authenticating until it finds one that works:
First, it first tries to authenticate using an EnvironmentCredential. This will look for certain environment variables needed to authenticate. Depending on the variables it finds, it will end up creating a ClientSecretCredential (to authenticate using the app's client secret) or ClientCertificateCredential (using an app's client certificate).
Next, if no suitable environment variables were found, it tries to authenticate using ManagedIdentityCredential, which (as the name suggests) will attempt to use a managed identity. For you, this will always succeed (because there is a managed identity available to be used).
Next, if no managed identity was available, it will continue trying with various other options. This is all described in detail in the documentation.
For your scenario, you basically have two options today.
Option 1, using DefaultAzureCredential and environment variables
Place the credentials for your multi-tenant app registration in environment variables and your existing code will "just work".
For example:
AZURE_TENANT_ID - the target tenant where you want to obtain the token
AZURE_CLIENT_ID - your multi-tenant app registration's app ID
AZURE_CLIENT_SECRET - a client secret for your multi-tenant app registration
Note: In general, it is better to use a certificate than a secret.
Option 2, using ClientSecretCredential or ClientCertificateCredential
Instead of using DefaultAzureCredential, you can directly create a ClientSecretCredential or a ClientCertificateCredential. You'll need to store the credential somewhere safe.
For example, on approach you could follow which would avoid any credentials in environment variable or in code:
Store credentials for your multi-tenant app in a key vault.
Allow your function's managed identity to access the credential in the key vault.
In your function, use your managed identity to retrieve the credential from Key Vault (e.g. using ManagedIdentityCredential), in your tenant.
Use that credential to authenticate as your multi-tenant app registration, in the target tenant (i.e. ClientSecretCredential or ClientCertificateCredential)
https://learn.microsoft.com/en-us/dotnet/api/overview/azure/identity-readme
Managed Identities can only get tokens within the tenant that they exist in.
You'd need a "traditional" multi-tenant app registration + client certificate/secret for this case.
Problem statement: retrieve and use a sensitive value (say a database connection string) stored in azure key vault programmatically in a web/console c# app.
I understand you can register an app in AAD and use its client id and client secret to generate an ad token programmatically which can be used to call/access azure key vault secrets.
My confusion is that the client secret itself is a sensitive ‘password’ which you would want to store in the key vault. Once someone knows the client secret they can access all secrets in the key vault. So how does it make sense to create a new secret (client secret) to store and access the original secret? (Can someone pls explain the logic behind this? Thanks!
This is the problem of bootstrapping.
How do you get access to the secret store without using a secret?
If you run your app in Azure, the answer is pretty simple.
Use Managed identities.
If not running in Azure, an interactive app can access Key Vault on behalf of the current user.
This does require that the user has access to the Key Vault secrets.
Another approach would be to use a certificate instead of a client secret.
If you host your app on Azure, you can use managed identities to perform authentication between services.
Once the Azure configuration is done, you need to add the following in your app: it only need to store the Azure KeyVault URI - as env variable would be better.
The following code uses Azure KeyVault with the AppConfiguration, so the local appsettings.json file is empty:
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.ConfigureAppConfiguration((hostingContext, configBuilder) =>
{
if (hostingContext.HostingEnvironment.IsDevelopment()) return;
AddAzureKeyVault(configBuilder);
var configRoot = configBuilder.Build();
AddAzureAppConfiguration(configBuilder, configRoot);
});
webBuilder.UseStartup<Startup>();
});
private static void AddAzureKeyVault(IConfigurationBuilder configBuilder)
{
var azureServiceTokenProvider = new AzureServiceTokenProvider();
var keyVaultClient = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(azureServiceTokenProvider.KeyVaultTokenCallback));
var keyVaultName = Environment.GetEnvironmentVariable("KEY_VAULT_NAME");
configBuilder.AddAzureKeyVault(
$"https://{keyVaultName}.vault.azure.net/",
keyVaultClient,
new DefaultKeyVaultSecretManager());
}
private static void AddAzureAppConfiguration(IConfigurationBuilder configBuilder, IConfigurationRoot configRoot)
{
var appConfigName = configRoot["AppConfiguration-Name"];
configBuilder.AddAzureAppConfiguration(appConfigName);
}
I have client applications that generate data that is encrypted on the device. The encryption keys are sent over HTTPS to an Azure Function that uses the Azure Key Vault to store the encryption keys so that other authorized clients can get them and decrypt the data.
Here is the piece of code in my Azure Function to perform the job of saving the encryption keys (the secret) in the key vault:
public async Task InsertEncryptionKeyAsync(EncryptionKeyContract encryptionKey)
{
Guard.CheckForNull(encryptionKey, nameof(encryptionKey));
// Serializes the encryption key information.
string serializedEncryptionKey = JsonConvert.SerializeObject(encryptionKey);
// Pushes the encryption to the Key-Vault.
var client = new KeyVaultClient(/* Key-vault- access otken*/, new HttpClient());
await client.SetSecretAsync(Constants.Vault.Url, encryptionKey.FileId.ToKey(), serializedEncryptionKey);
}
Question
Is this design an appropriate use of the Azure Key Vault?
Yes, Azure Key Vault is ment to store keys / secrets so this is a perfect valid use case. Depending on who are your clients and what permission they have, you could get rid of the middleware and let the client add the encryption key to the vault. But usually you don't want to grant clients permission to the vault so doing that using a Azure Function looks also fine.
I agree with Martin, this is a great use case for KeyVault.
You may wish to lean further on keyvault for it's abilities.
Assuming you are using symmetric encryption on the client, keyvault has wrapKey which is made for storing keys.
Using wrapKey / unwrapKey mean your clients can be granted specific permissions using access policies on what they can do with the keys.
You may also wish to sign and verify the encrypted content to ensure it has not been manipulated on transit.
If you haven't already gone down this route you may wish to investigate Native App AD authentication https://learn.microsoft.com/en-us/azure/active-directory/develop/active-directory-authentication-scenarios and https://learn.microsoft.com/en-us/azure/active-directory/develop/active-directory-application-objects which can work on behalf of a user or service principal and thus provide a nice storey for how keyvault access control might work.... but I won't go into detail, as I'm going of track the original question. Notably key vault has a limit of 16 access policies so it's more for an application group not for individual installs or users.
I was trying to achieve the following scenario:
Create a service bus queue via the API.
Send a message to that queue.
Using C#.
If it was in AWS, I would need a couple of lines of code, and I need 3 pieces of information which are very easy to get, but it doesn't seem to be the case in Azure...
The pieces of information I realized that I need so far:
ClientId
ClientSecret
ServiceBusConnectionString
ServiceBusNamespace
QueueName
ResourceGroupName
TenantId How to get the azure account tenant Id?
SubscriptionId
After hours of research, I was able to collect everything apart from the first two items, ClientId and ClientSecret, where I can get them?
EDIT
Which one is the client ID?
Thanks
You have to register your app in Azure AD to get your clientid & client secret.
Follow these steps to register in AAD. Note down the key (client secret) as it will not appear again anywhere.
Synopsis:
Login # Azure portal
Search 'Azure Active Directory' on top bar & open
Go to 'App Registrations' & add your app
Get the client id (this is Application ID & NOT Object ID) & add key (this is client secret)