dotnet / aspire

An opinionated, cloud ready stack for building observable, production ready, distributed applications in .NET
https://learn.microsoft.com/dotnet/aspire
MIT License
3.61k stars 403 forks source link

[WebToolsE2E] After wiring up ASP.NET Core app with Aspire to Azure Key Vault and deploy to ACA, unable to connect to Azure Key Vault successfully from the published container app #2621

Closed balachir closed 5 months ago

balachir commented 6 months ago

REGRESSION INFO: We're using a new way in Aspire 8.0 P4 of connecting to KeyVault using AddConnectionString, so cannot really compare with Aspire 8.0 P3

INSTALL STEPS

  1. Clean machine: Win11 x64 23h2 ENU
  2. Install VS 17.10 Preview 2 build rel.d17.10-34701.141 (includes .NET SDK 8.0.200 and .NET workload Aspire 8.0.0-preview.4.24129.7)

REPRO STEPS

  1. Create ASP.NET Core project using Empty template with name 'AspireAzureKeyVault01' and Aspire enabled, build and run
    • works fine
  2. In the AspireAzureKeyVault01.AppHost project, wire up the Connection string to connect to the Azure Key Vault
    • Open appsettings.json of AspireAzureKeyVault01.AppHost project, add the following section with the connection string
    • Open Program.cs of AspireAzureKeyVault01.AppHost project and add the connection string to the builder and pass it as a reference to AspireAzureKeyVault01 project
  "ConnectionStrings": {
    "secretConnectionName": "https://<vault_account_name>.vault.azure.net/"
  },
var keyVault = builder.AddConnectionString("secretConnectionName", "VaultUri");
builder.AddProject<Projects.AspireAzureKeyVault01>("aspireazurekeyvault01")
    .WithReference(keyVault);
  1. In the AspireAzureKeyVault01 project, install the Aspire.Azure.Security.KeyVault NuGet package to read the VaultUri environment variable from Configuration, connect to the KeyVault and load the secret called myAppSetting
    • Right-click EmptyApp5withAspire > Add Aspire Component > Aspire.Azure.Security.KeyVault version 8.0.0-preview.4.24129.7 (matching the version of Aspire that's installed)
    • Update Program.cs with the code as shown below
      
      using Azure.Identity;

var builder = WebApplication.CreateBuilder(args);

builder.AddServiceDefaults();

var keyVaultEndpoint = new Uri(Environment.GetEnvironmentVariable("VaultUri")); builder.Configuration.AddAzureKeyVault(keyVaultEndpoint, new VisualStudioCredential());

var app = builder.Build();

app.MapDefaultEndpoints();

//app.MapGet("/", () => "Hello World!"); string? _mySecret = builder.Configuration["myAppSetting"]; var result = string.IsNullOrEmpty(_mySecret) ? "Null" : _mySecret; app.MapGet("/", async context => { await context.Response.WriteAsync($"Secret is {result}"); });

app.Run();


4. Build and run the solution locally, it works fine and the secret gets loaded from the Azure Key Vault
![image](https://github.com/dotnet/aspire/assets/8246794/dba77f41-055c-4a2a-afee-ecb4689184b5)

5. Publish the solution using `azd init` and `azd up`
- During `azd up`, it prompts us to enter the value for `secretConnectionName`. Enter the full vault uri again in the format `https://<vault_account_name>.vault.azure.net/`
![image](https://github.com/dotnet/aspire/assets/8246794/3a6de30c-bb42-4704-9446-6307273932cd)

- The `azd up` completes successfully. Then try to navigate to the published application in the browser

**ACTUAL**
- The published app does not load
![image](https://github.com/dotnet/aspire/assets/8246794/87de9e40-9f37-47bb-93c2-5e3a9b61013c)

- The portal shows a ContainerCrashing error
![image](https://github.com/dotnet/aspire/assets/8246794/ca85fcb3-8cbe-4f2e-9920-b87ad57d4fa5)

- Console log shows a WebSocketConnectionError
![image](https://github.com/dotnet/aspire/assets/8246794/d6341bdf-8f5e-481d-965f-1391f3990285)

- Environment variables shows the wrong value for VaultUri
![image](https://github.com/dotnet/aspire/assets/8246794/075f0f74-e2e2-4fd0-b2ea-316e57ca31e6)

**EXPECTED**
The published application to load successfully and retrieve the secret from the Key Vault
balachir commented 6 months ago

@rajeshkamal5050 @davidfowl, I'm not sure if the issue here is in aspire or azd. So, I'm starting with the aspire repo.

cc: @eerhardt

rajeshkamal5050 commented 6 months ago

@rajeshkamal5050 @davidfowl, I'm not sure if the issue here is in aspire or azd. So, I'm starting with the aspire repo.

cc: @eerhardt

@vhvb1989 can you check if anything related to azd?

eerhardt commented 6 months ago

@balachir - what is this line?

builder.Configuration.AddAzureKeyVault(keyVaultEndpoint, new VisualStudioCredential());

Are you leaving that in for the published app? VisualStudioCredential only works when you are running from VS.

eerhardt commented 6 months ago

Note also, you need to assign a role assignment of the new ACA App as a "Key Vault Reader" to the existing Key Vault you are using.

vhvb1989 commented 6 months ago

Note also, you need to assign a role assignment of the new ACA App as a "Key Vault Reader" to the existing Key Vault you are using.

@eerhardt the role assignment should happen automatically. azd creates a managedIdentity and gives reading access to it. Then, a project (the aca App) uses that ManagedIdentity.

Looks like the only issue here is the use of the wrong credential.

eerhardt commented 6 months ago

the role assignment should happen automatically. azd creates a managedIdentity and gives reading access to it. Then, a project (the aca App) uses that ManagedIdentity.

How can it do that to an existing Azure KeyVault? azd doesn't even know that the connection string is a KeyVault.

vhvb1989 commented 6 months ago

mm, I guess I got confused with builder.AddAzureKeyVault("foo").

IIRC, that's for creating a new Key Vault. That's different than builder.Configuration.AddAzureKeyVault(""), then?

eerhardt commented 6 months ago

In the repro steps, the AppHost code being used is:

var keyVault = builder.AddConnectionString("secretConnectionName", "VaultUri");
builder.AddProject<Projects.AspireAzureKeyVault01>("aspireazurekeyvault01")
    .WithReference(keyVault);

Which means "I have an existing Key Vault that I want my app to use".

vhvb1989 commented 6 months ago

Interesting. @eerhardt , what's the benefit of adding the connection string for a keyVault to the AppHost and then pulling the endpoint from the env with Environment.GetEnvironmentVariable("VaultUri") from a project for pulling secrets from that key vault?

I'm trying to understand why I want to prefer this v/s using the VaultUri directly from my project? (specially if the app container that is publish won't have read access to my existing kv).

Is it that maybe connectionString is a better fit for secrets? like a db connection that includes a connection key?

I don't see why making the project like:

builder.Configuration.AddAzureKeyVault("VaultUri", new DAC());

Would be any difference... The vaultUri doesn't need to be a secret, and does not give read access to secrets from it.

davidfowl commented 6 months ago

How can it do that to an existing Azure KeyVault? azd doesn't even know that the connection string is a KeyVault.

this issue is assigned to me but I’m hoping @mitchdenny ’s CDK work let us model this more readily

eerhardt commented 6 months ago

what's the benefit of adding the connection string for a keyVault to the AppHost

Because then it can be used in multiple service projects. You only need to set it in one place (the AppHost) and you can WithReference it to multiple projects.

and then pulling the endpoint from the env with Environment.GetEnvironmentVariable("VaultUri")

I don't know why this was used in the repro steps. Those two lines should just be:

-var keyVaultEndpoint = new Uri(Environment.GetEnvironmentVariable("VaultUri"));
-builder.Configuration.AddAzureKeyVault(keyVaultEndpoint, new VisualStudioCredential());
+builder.Configuration.AddKeyVaultSecrets("VaultUri");
vhvb1989 commented 6 months ago

Because then it can be used in multiple service projects. You only need to set it in one place (the AppHost) and you can WithReference it to multiple projects.

Can service projects pull the key vault endpoint from the system environment? Or from a project setting constants?

Using a key vault endpoint as a secret connection strings seems a little bit odd. Specially if I need to call builder.Configuration.AddKeyVaultSecrets("VaultUri") from the service project's code. Maybe, it would make a lot of sense, if Aspire could automatically add the key vault to the Configuration without manually calling Configuration.AddKeyVaultSecrets(..).

Like, changing this:

var keyVault = builder.AddConnectionString("secretConnectionName", "VaultUri");
builder.AddProject<Projects.AspireAzureKeyVault01>("aspireazurekeyvault01")
    .WithReference(keyVault);

for:

var keyVault = builder.AddConnectionString("secretConnectionName", "VaultUri");
builder.AddProject<Projects.AspireAzureKeyVault01>("aspireazurekeyvault01")
    .WithConfigKeyVault(keyVault);

A method like WithConfigKeyVault() would allow Aspire to know that's a Key Vault to be set as App Configuration, so we need to assign read access for the ManagedIdentity and set the AppConfig.

eerhardt commented 6 months ago

Can service projects pull the key vault endpoint from the system environment? Or from a project setting constants?

The service projects call pull the key vault endpoint from wherever the configuration is set up, just like any other connection string.

Maybe, it would make a lot of sense, if Aspire could automatically add the key vault to the Configuration without manually calling Configuration.AddKeyVaultSecrets(..).

Feel free to open an issue suggesting this.

davidfowl commented 6 months ago

@vhvb1989 See https://github.com/dotnet/aspire/issues/2587. There are many scenarios with Keyvault varying from the application code using it to the compute fabric (aka the apphost in local dev) pulling from kv and pushing things into the app via environment variables.

balachir commented 6 months ago

and then pulling the endpoint from the env with Environment.GetEnvironmentVariable("VaultUri") I don't know why this was used in the repro steps.

@eerhardt regarding the above question, I was trying to mimic what the VS Connected Service tooling does. Today, in a non-aspire app, when I try to add an Azure Key Vault using the VS Connected Services UI, it adds the VaultUri as an environment variable in launchSettings.json and then pass that to Configuration. I'll try using Configuration.AddKeyVaultSecrets instead like you suggested and see if I can get that to work.

balachir commented 6 months ago

@eerhardt I tried using Configuration.AddKeyVaultSecrets now but I cannot get F5 to work now due to an AccessDenied error. Is this a separate bug that I should open? Or am I missing something?

Details I did similar steps like shown in my Repro Steps above but made the following tweaks.

In WebApp01.AppHost project, I changed it like this image

In WebApp01 project, I changed it like this image

When I run the project, I see the following error image

eerhardt commented 6 months ago

image

It says you don't have permission to the KeyVault. You need to enable it. https://learn.microsoft.com/en-us/azure/key-vault/general/rbac-guide

eerhardt commented 6 months ago

Moving this to preview5 since it doesn't look like an issue with the product.

eerhardt commented 5 months ago

I'm going to close this issue as it looks like an Azure permission/configuration issue. @balachir - please re-open if Azure is set up correctly and if there is something Aspire is doing wrong.