0

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.

enter image description here

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"));
                });
        }
4
  • It would be helpful to know what errors you're seeing and what the message/stack trace look like. Commented Apr 10, 2024 at 20:05
  • I edited my post with a picture of the error that happens upon startup. That's really all the info I get.. If I just replace the < secret > placeholder in local.settings.json with the actual correct connection string value, it all works. But I would like to keep doing the < secret > convention which worked previously Commented Apr 10, 2024 at 20:10
  • @kdeez try using keyvualt reference when you want to save your connection string in key vault and call the secret in local settings. check this learn.microsoft.com/en-us/azure/app-service/… Commented Apr 11, 2024 at 3:27
  • 1
    My problem is strictly for local development, for all our deployments we have build scripts which insert the correct function configuration values for startup. So a managed identity solution won't really work here. Commented Apr 11, 2024 at 4:18

1 Answer 1

0

I wouldn't expect the config that you've shared to work locally nor anywhere. The value <secret> will be interpreted as a connection string and fail to parse, which matches the error that you're seeing.

To work, something in your local environment has to tell the trigger what Service Bus namespace to connect to and how to authenticate. There's no way around that for any hosting model. In-Proc and Isolated both require this.

You can do this without a secret locally by using an Identity-based authentication, for which you'd create a local a setting called ServiceBus:ConnectionString__fullyQualifiedNamespace that points to your Service Bus endpoint. That will use DefaultAzureCredential behind the scenes and allow you to use Visual Studio, the Azure CLI, or another development-local asset to authenticate you. (see: Authenticate the client and DefaultAzureCredential)

Your production deployment could continue to use the connection string form, if desired, or share the identity format with a production-quality credential, such as managed identity. (see: Create Microsoft Entra credential types using configuration files)

Sign up to request clarification or add additional context in comments.

4 Comments

Thank you for the response. I am confused though, when you say "there's no way around that for any hosting model", i am confused, because this is exactly what i was doing for in-proc and it was working fine..
Something, somewhere had to be providing the data for where the trigger should connect to. If the trigger cannot connect, it will complain and the Function won't start. I don't have enough context to understand where it was getting that information previously, but the string <secret> would never had been accepted, as it fails parsing. see: github.com/Azure/azure-sdk-for-net/blob/main/sdk/eventhub/…
Previously we were getting the data from ConfigureAppConfiguration() in Startup.cs. Which I then ported over to Program.cs's ConfigureAppConfiguration(), posted above. I edited the post with more context of what we were doing previously, if that helps...
That makes sense. AppConfig is providing the value for development and overriding the invalid token. It seems the root of your current problem is the local value isn't getting overwritten by AppConfig. At a glance, I don't see anything obviously wrong in the new code, but I also don't know the low-level details of how the host integrates with the AppConfig provider. You may want to consider asking in an issue here: github.com/Azure/AppConfiguration-DotnetProvider

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.