I am porting a .NET 6 in-process Azure Function app, to .NET 8 with dotnet-isolated.
We do not store our secrets in source control/locally. So for local development, previously, we used placeholder values in local.settings.json:
"ServiceBus:ConnectionString": "<secret>"
which we would overwrite in Startup.cs with a KeyVault configuration. Then our Function input bindings would have a reference to the setting for ConnectionStrings/etc:
[Function("MyFunction")]
public async Task Run([ServiceBusTrigger(
"%ServiceBus:Topic%",
"%ServiceBus:Subscription%",
Connection = "ServiceBus:ConnectionString")]
string sbMessage)
{
// does stuff
// (this example is after already moving to .NET 8/isolated
// but the function binding is essentially the same as before)
}
and it would all work great.
After switching to dotnet-isolated, my functions now throw errors on startup, because it seems the function binding is still being passed the value "<secret>", instead of the actual value from the KeyVault.
If i simply replace "<secret>" in local.settings.json with the actual correct connection string value, it all works. But I need to keep using this <secret> convention.
I have replicated that startup functionality in .NET 8's Program.cs:
var host = new HostBuilder()
.ConfigureFunctionsWorkerDefaults()
.ConfigureAppConfiguration((context, builder) =>
{
var environment = context.HostingEnvironment;
if (environment.IsDevelopment())
{
builder.AddEnvironmentVariables();
var builtConfig = builder.Build();
var secretClient = new SecretClient(
new Uri($"https://{builtConfig["KeyVault:Name"]}.vault.azure.net/"),
new DefaultAzureCredential());
builder.AddAzureKeyVault(secretClient, new KeyVaultSecretManager());
}
})
.ConfigureServices((hostContext, services) =>
{
// does stuff
})
.Build();
host.Run();
When I debug ConfigureAppConfiguration(), I can see that the builder does have the KeyVault configuration correctly applied. "ServiceBus:ConnectionString" is set to the correct value, and then when I step into ConfigureServices(), I can see that the hostContext.Configuration also has the correct KeyVault values applied.
How do I correctly get the function input bindings to respect the overlaid KeyVault configuration on startup?
For reference, here is how we were doing it before, in-proc, which was working fine:
public class Startup : FunctionsStartup
{
private IConfiguration _configuration;
public IConfiguration Configuration
{
get => _configuration ?? throw new Exception("Tried accessing uninitialized configuration");
set => _configuration = value;
}
public override void ConfigureAppConfiguration(IFunctionsConfigurationBuilder builder)
{
var context = builder.GetContext();
builder.ConfigurationBuilder.AddEnvironmentVariables();
if (context.EnvironmentName == "Development")
{
var builtConfig = builder.ConfigurationBuilder.Build();
var secretClient = new SecretClient(
new Uri($"https://{builtConfig["KeyVault:Name"]}.vault.azure.net/"),
new DefaultAzureCredential());
builder.ConfigurationBuilder.AddAzureKeyVault(secretClient, new KeyVaultSecretManager());
}
Configuration = builder.ConfigurationBuilder.Build();
}
public override void Configure(IFunctionsHostBuilder builder)
{
services.AddAzureClients(x =>
{
x.AddServiceBusClient(Configuration.GetValue<string>
("ServiceBus:ConnectionString"));
});
}
