admin管理员组

文章数量:1279244

Setup

I'm running a .NET 6 web app inside an Azure Container App and accessing Azure Key Vault for secrets and connection strings. Dependency Injection works fine for injecting my custom Key Vault client (wrapper of Azure.Security.KeyVault.Secrets.SecretClient), and I can successfully retrieve secrets after Build() (even from within Program.cs).

Problem

I need to fetch a connection string from Key Vault before Build() so I can pass it to a service constructor when registering dependencies. The SecretClient.GetSecret(keyName) call fails at this point.

I don't face the same problem when I run locally.

Logs

When the Container app is trying to activate the revision (and runs the Program.cs), I see this error from the Container app Log stream:

What I've Tried

  • Ensuring Managed Identity is correctly assigned to the Container App.
  • Confirming Key Vault permissions (it works later, so permissions shouldn't be the issue)

Question

How can I securely retrieve a secret before calling Build() in a .NET 6 containerized web app? Or is there a better pattern for handling this scenario?

Any insights would be greatly appreciated!

Code

public class KeyVaultService : IKeyVaultService
{
    private readonly SecretClient _secretClient;

    public KeyVaultService(string keyVaultName)
    {
        _secretClient = new SecretClient(new Uri($"https://{keyVaultName}.vault.azure"), new DefaultAzureCredential());
    }

    public string GetSecret(string keyName)
    {
        KeyVaultSecret secret = _secretClient.GetSecret(keyName);
        return secret.Value;
    }
}

public class Program
{
    public static void Main(string[] args)
    {
        WebApplicationBuilder? builder = WebApplication.CreateBuilder(args);
        // ... configuring logging, etc ...

        var keyVault = new KeyVaultService(keyVaultName);
        // this is where it fails, but only in the Container app
        // locally works fine.
        string conn = keyVault.GetSecret("ConnectionString");

        // registering a service that needs connectionString in its constructor.

        var app = builder.Build();

        // after Build() the Key Vault works fine.
        keyVault = new KeyVaultService(keyVaultName);
        conn = keyVault.GetSecret("ConnectionString");

        // omitting unrelated code, like app.Run()
    }
}

UPDATE

I tried adding the Key Vault to Configuration and does not work in my Azure Container App (works locally though).

From logs I noticed that before Build() the builder.Configuration["ConnectionString"] returns null or empty string, so I guess that Key Vault was not loaded as a provider.

WebApplicationBuilder? builder = WebApplication.CreateBuilder(args);
builder.Configuration.AddAzureKeyVault(XXXXX, new DefaultAzureCredential());
builder.ConfigureDatabase(builder.Configuration["ConnectionString"]);
var app = builder.Build();

Setup

I'm running a .NET 6 web app inside an Azure Container App and accessing Azure Key Vault for secrets and connection strings. Dependency Injection works fine for injecting my custom Key Vault client (wrapper of Azure.Security.KeyVault.Secrets.SecretClient), and I can successfully retrieve secrets after Build() (even from within Program.cs).

Problem

I need to fetch a connection string from Key Vault before Build() so I can pass it to a service constructor when registering dependencies. The SecretClient.GetSecret(keyName) call fails at this point.

I don't face the same problem when I run locally.

Logs

When the Container app is trying to activate the revision (and runs the Program.cs), I see this error from the Container app Log stream:

What I've Tried

  • Ensuring Managed Identity is correctly assigned to the Container App.
  • Confirming Key Vault permissions (it works later, so permissions shouldn't be the issue)

Question

How can I securely retrieve a secret before calling Build() in a .NET 6 containerized web app? Or is there a better pattern for handling this scenario?

Any insights would be greatly appreciated!

Code

public class KeyVaultService : IKeyVaultService
{
    private readonly SecretClient _secretClient;

    public KeyVaultService(string keyVaultName)
    {
        _secretClient = new SecretClient(new Uri($"https://{keyVaultName}.vault.azure"), new DefaultAzureCredential());
    }

    public string GetSecret(string keyName)
    {
        KeyVaultSecret secret = _secretClient.GetSecret(keyName);
        return secret.Value;
    }
}

public class Program
{
    public static void Main(string[] args)
    {
        WebApplicationBuilder? builder = WebApplication.CreateBuilder(args);
        // ... configuring logging, etc ...

        var keyVault = new KeyVaultService(keyVaultName);
        // this is where it fails, but only in the Container app
        // locally works fine.
        string conn = keyVault.GetSecret("ConnectionString");

        // registering a service that needs connectionString in its constructor.

        var app = builder.Build();

        // after Build() the Key Vault works fine.
        keyVault = new KeyVaultService(keyVaultName);
        conn = keyVault.GetSecret("ConnectionString");

        // omitting unrelated code, like app.Run()
    }
}

UPDATE

I tried adding the Key Vault to Configuration and does not work in my Azure Container App (works locally though).

From logs I noticed that before Build() the builder.Configuration["ConnectionString"] returns null or empty string, so I guess that Key Vault was not loaded as a provider.

WebApplicationBuilder? builder = WebApplication.CreateBuilder(args);
builder.Configuration.AddAzureKeyVault(XXXXX, new DefaultAzureCredential());
builder.ConfigureDatabase(builder.Configuration["ConnectionString"]);
var app = builder.Build();
Share Improve this question edited Feb 26 at 15:29 spiros asked Feb 25 at 10:04 spirosspiros 3815 silver badges11 bronze badges 2
  • 1 Please share your code that you've tried. – Sirra Sneha Commented Feb 25 at 10:13
  • @SirraSneha, I added the code in the post – spiros Commented Feb 25 at 10:29
Add a comment  | 

1 Answer 1

Reset to default 1

The overall design of loading configuration, registering services, and using IOptions to expose that configuration to services has gone through a number of revisions during the development of dotnet core. So there are many ways to solve this design problem.

Personally I would implement something like the following. Adding all secrets in the vault as configuration values, then follow the "options pattern" to inject those options into services.

builder.Configuration.AddAzureKeyVault(
    new Uri(builder.Configuration["AzureKeyVaultConnectionString"]),
    new DefaultAzureCredential()
);
builder.Services.Configure<OptionsType>(
    builder.Configuration.GetSection("SectionName")
);

However that will depend on exactly how your connection string is being configured. When calling WebApplication.CreateBuilder, "only configuration that is necessary for the host" will be loaded immediately. This certainly includes any ASPNETCORE_ environment variables.

The remaining "application configuration providers" will be registered and only loaded while building the web application. You can follow this pattern yourself to register additional application configuration;

builder.ConfigureAppConfiguration((context, builder) => {
    builder.AddAzureKeyVault(
        new Uri(context.Configuration["AzureKeyVaultConnectionString"]),
        new DefaultAzureCredential()
    );
});

Similarly you can delay registering services;

builder.ConfigureServices((context, services) => {
    // etc
});

Though I would probably just tweak how you are passing in your connection string to ensure it is considered "host configuration".

You should never need to create any instance of your services manually. If you think you need to create a service before calling .Build, then you are mistaken. If you explain what you are actually trying to do, then I could explain how you are supposed to achieve what you want. Either during the process of building the host, during startup of the host, or lazily when your service dependency is first required.

本文标签: cAzure Key Vault Not Accessible before Build() in NET 6 Container AppStack Overflow