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);
}
Related
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
I have the following approach to connect to the key vault in my code:
public static class ConfigurationBuilderExtension
{
public static IConfigurationBuilder AddKeyVaultconfiguration(this IConfigurationBuilder builder)
{
var config = builder.Build();
string name = config["KeyVault:Name"];
string clientId = config["KeyVault:ClientId"];
string clientSecret = config["KeyVault:ClientSecret"];
Verify.NotNullOrEmpty(name, nameof(name));
Verify.NotNullOrEmpty(name, nameof(clientId));
Verify.NotNullOrEmpty(name, nameof(clientSecret));
builder.AddAzureKeyVault($"https://{name}.vault.azure.net/", clientId, clientSecret);
return builder;
}
}
To run this locally, I just adding the user secrets to the project:
{
"KeyVault": {
"Name": "brajzorekeyvault",
"ClientId": "xxxx",
"ClientSecret": "xxxx"
}
}
This works locally.
However, how do I use this approach when I publish this to a app service in Azure? I must in somehow inject the name, clientId and clientSecret? But I don't know which is the best practice approach to do this? Should I create a variable group in Azure devops that consists of these values, and then use these in the piplines?
You should use a managed identity to access the key vault inside the web app to avoid having to inject a secret. See this tutorial
For local development you can link an account in VS that will be used to authenticate against the key vault. See the docs
Using managed identities is best practices, in fact you won't see any offical docs about connecting using the client secret withouth at least a warning to use managed identities.
When we are working with PAAS (Most of resources are PAAS in Azure like App service and KeyVault) in Azure. Most of the time, We do not need to write code or write very less code to access them such as in this case. Do not need to provide any detail like connection string or other details to access key vault in app service, Just do little configuration in azure portal and direct get secret from key vault.
In Azure , they are resources in same resource group which can communicate to each other or authenticate or authorize by just adding object id of app service to keyvault "Access policies". All the headache is on azure of validation. It is very easy, secure and good practice.
Steps are as Follows
Enable Managed Service Identity for your Web App/App Service
a. Select "Identity" from the left-side menu in the Azure Web App/App Service.
b. In the System-assigned tab, Change the "Status" toggle to "On".
c. After a few seconds, Object ID will be available then copy the "Object ID".
Authorize the Web App/App Service to access Your Key Vault
a. Select "Access policies" from the "Key Vault" screen.
b. Click "Add Access Policy".
c. Provide the "Get" and "List" permissions.
d. In the “Select a Principal” option, specify the value for the "Object ID" you
copied earlier for the Azure Web App/App Service.
e. Paste, search and then select it from the list.
f. Click "Add".
g. Click "Save" to persist the changes and complete the process.
Provide Permissions
Copy object Id
Read Azure Key Vault Secrets in .NET Core
a. Install the NuGet Packages
You may install these packages in one of two ways: Either via the NuGet
Package Manager integrated into the Visual Studio 2019 IDE or by running the
following command(s) in the Package Manager Console:
Install-Package Microsoft.Extensions.Azure and Install-Package Azure.Security.KeyVault.Secrets
Note :- In My case , I did not need to install these packages.
b. Access Secrets from AzureKeyVault
Specify the Vault Uri in AppSettings :- Create a section named "KeyVault" in the appsettings.json file and specify a key named "VaultUri" in there as shown below.
appsettings.Development.json
{
"SecretName": "xyz"
}
appsettings.OtherEnv.json
"KeyVault": {
"VaultUri": "https://yourkeyvaulturl.vault.azure.net/"
}
Create KeyVaultManagement class
public class KeyVaultManagement
{
private readonly IConfiguration _config;
public KeyVaultManagement(IConfiguration config)
{
_config= config;
}
public SecretClient SecretClient
{
get
{
return new SecretClient(
new Uri($"{this._config["KeyVault:VaultUri"]}"),
new DefaultAzureCredential()) ;
}
}
}
Write the below code in Program.cs
.ConfigureAppConfiguration((context, config) =>
{
var builtConfig = config.Build();
if (!context.HostingEnvironment.IsDevelopment())
{
config.AddAzureKeyVault(new KeyVaultManagement(builtConfig).SecretClient, new KeyVaultSecretManager());
}
});
Write the below where need to fetch secret.
var valueofSecret = configuration["SecretName"];
configuration is IConfiguration
Documentation of Microsoft for Access Policy of Azure Keyvault
IF YOUR APPLICATION IS NOT APP SERVICE OR IT IS DEVELOPED IN .NET FRAMEWORK
Then Implement KEYVAULT using Certificate
Install Same Nuget Packages
Write the below code in Program.cs. Might need to add package or namespace to use certificate classes
.ConfigureAppConfiguration((context, config) =>
{
var root = config.Build();
var KeyVaultName = root["KeyVaultName"];
var Uri = new Uri($"https://{KeyVaultName}.vault.azure.net/");
var x509Certifcate = CertifcateHelper.GetCertificate(root["Thumprint"],"KeyVaultCertificate");
config.AddAzureKeyVault(Uri , new ClientCertificateCredential(root["ClientTenantId"], root["ClientAppId"], x509Certifcate));
});
Add the below lines in APPSetting
appsettings.Development.json
{
"KeyVaultName": "keyvalutname",
"ClientTenantId": "Get from azure and paste here",
"ClientAppId": "Get from azure and paste here",
"Thumbprint": "Get from keyvalut certificate in azure and paste here";
}
Write the below where need to fetch secret.
var valueofSecret = configuration["SecretName"];
I have setup an OpenID Connect provider using Azure B2C to enable my iOS users to sign in my application using Sign-in with Apple.
I followed the instructions and it is working great.
I have to update the token regularly and I am trying to automate it.
I have set up an Azure Function that will be triggered on a schedule and it generates a new token.
Now I do not know how to update the client secret from the Azure Function itself. I am not sure which API to use...
Question
How can I update the client secret field of the external provider I have set up?
Update
The graph API seems to be what I need but I do not know how to reconcile a user and the configuration of an Azure AD B2C service ...
You could update the client secret with Graph API. See the code sample about calling the Microsoft Graph API.
// Read application settings from appsettings.json (tenant ID, app ID, client secret, etc.)
AppSettings config = AppSettingsFile.ReadFromJsonFile();
// Initialize the client credential auth provider
IConfidentialClientApplication confidentialClientApplication = ConfidentialClientApplicationBuilder
.Create(config.AppId)
.WithTenantId(config.TenantId)
.WithClientSecret(config.AppSecret)
.Build();
ClientCredentialProvider authProvider = new ClientCredentialProvider(confidentialClientApplication);
// Set up the Microsoft Graph service client with client credentials
GraphServiceClient graphClient = new GraphServiceClient(authProvider);
// call Update identityProvider API
var identityProvider = new IdentityProvider
{
ClientSecret = "1111111111111"
};
await graphClient.IdentityProviders["{id}"]
.Request()
.UpdateAsync(identityProvider);
I have developed some application and I am using there a external service to send emails (SendGrid). To use this service I need login and password to use SendGrid api. In developmnet environment I have used .Net Core secrets (https://learn.microsoft.com/pl-pl/aspnet/core/security/app-secrets?view=aspnetcore-2.2&tabs=windows) but now after publish I need to deliver login and password in other way. I know that there is a Azure Key Vault, but what if app will be published somwhere else?
FOR PRODUCTION: Use Managed Identity for Azure Resources to avoid storing connection strings. It is a clean solution for managing secrets in Key Vault.
Access Key for Key Vault or other similar resources is never shared, not even to Services or DevOps Engineer to deploy or run the applications in production
Create a Azure Key Vault and store your secrets
Enable System Managed Identity in your Azure App Service. It will register your web application at AAD internally
Search for Managed Service Identity Application ID at Key Vault and allow intended access to it
Initialize connection to your Key Vault by following snippet
Program.cs
using Microsoft.Azure.Services.AppAuthentication;
using Microsoft.Azure.KeyVault;
using Microsoft.Extensions.Configuration.AzureKeyVault;
...
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((context, config) =>
{
if (context.HostingEnvironment.IsProduction())
{
var builtConfig = config.Build();
var azureServiceTokenProvider = new AzureServiceTokenProvider();
var keyVaultClient = new KeyVaultClient(
new KeyVaultClient.AuthenticationCallback(
azureServiceTokenProvider.KeyVaultTokenCallback));
config.AddAzureKeyVault(
$"https://{builtConfig["KeyVaultName"]}.vault.azure.net/",
keyVaultClient,
new DefaultKeyVaultSecretManager());
}
})
.UseStartup<Startup>();
Startup.cs
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
}
Now, secrets from Key Vault will be loaded into Configuration variable. You can use it like, Configuration["YourKeyVaultKeyName"].
FOR DEVELOPMENT: You can optionally use Dotnet-user-secrets tool to store\ manage the secrets in local machine.
I am using the following code to try to programmatically register an application in Azure Active Directory:
var application = azure.ActiveDirectoryApplications.Define(applicationName)
.WithSignOnUrl(url)
.WithIdentifierUrl(url)
.WithAvailableToOtherTenants(false)
.DefinePasswordCredential(id)
.WithPasswordValue(secret)
.Attach()
.Create();
Where azure is an instance of Microsoft.Azure.Management.Fluent.Azure.
When I run the above to create an Azure Active Directory Application, an Microsoft.Azure.Management.Fluent.Azure exception is thrown with the message Operation returned an invalid status code 'Forbidden'. Creation of other Azure resources (like resource groups and app services) work just fine.
Looking at the exception details, I can see that a request is made to the following endpoint:
https://graph.windows.net/{myTenantId}/applications?api-version=1.6
The following is in the response body:
{"odata.error":{"code":"Authorization_RequestDenied","message":{"lang":"en","value":"Insufficient privileges to complete the operation."}}}
Since the bodies says "Insufficient privileges to complete the operation", it appears to be a simple permission issue, but I have granted the following permissions (while signed in as a global administrator) for the Microsoft.Azure.ActiveDirectory API for the application that's running the code:
Access the directory as the signed-in user
Read and write directory data
Are these privileges not enough? What am I missing? As I said, creation of other resources using the fluent API works just fine.
The scope Directory.AccessAsUser.All and Directory.ReadWrite.All User.Read is sufficient permission to create applications in the Azure Active Directory. Since you doesn't provide how you construct the azure instance, I would provide a working code sample:
static void Main(string[] args)
{
var url = "http://adfei.onmicrosoft.com/appFluent";
var id = "abc";
var secret = "secret";
var applicationName = "appFluent";
var credFile = new AzureCredentials(new UserLoginInformation
{
ClientId = "{appId of native application}",
UserName = "{userName}",
Password = "{password}"
},
"adfei.onmicrosoft.com", AzureEnvironment.AzureGlobalCloud);
IAzure azure = Azure.Authenticate(credFile).WithDefaultSubscription();
var application = azure.ActiveDirectoryApplications.Define(applicationName)
.WithSignOnUrl(url)
.WithIdentifierUrl(url)
.WithAvailableToOtherTenants(false)
.DefinePasswordCredential(id)
.WithPasswordValue(secret)
.Attach()
.Create();
Console.Read();
}
And please ensure the scope is include in the access token to ensure that you have the permission for this operation. You can capture the request via Fiddler to check the token and decode the token from this site to check scp claims in the access token.