-
Notifications
You must be signed in to change notification settings - Fork 720
Description
Description
When you have 2 projects talking to the same Azure SQL Server, and you use an ACA environment, each app will get a separate managed identity.
This causes problems with Azure SQL Server because the way .NET Aspire grants access to the SQL Server is via setting the SQL Admin:
aspire/src/Aspire.Hosting.Azure.Sql/AzureSqlExtensions.cs
Lines 213 to 229 in 73459e9
| // Creating a new SqlServer instance requires an administrator, | |
| // so we need to create one here using the empty PrincipalId/PrincipalName | |
| var principalIdParameter = new ProvisioningParameter(AzureBicepResource.KnownParameters.PrincipalId, typeof(string)); | |
| infrastructure.Add(principalIdParameter); | |
| var principalNameParameter = new ProvisioningParameter(AzureBicepResource.KnownParameters.PrincipalName, typeof(string)); | |
| infrastructure.Add(principalNameParameter); | |
| return new SqlServer(infrastructure.AspireResource.GetBicepIdentifier()) | |
| { | |
| Administrators = new ServerExternalAdministrator() | |
| { | |
| AdministratorType = SqlAdministratorType.ActiveDirectory, | |
| IsAzureADOnlyAuthenticationEnabled = true, | |
| Sid = principalIdParameter, | |
| Login = principalNameParameter, | |
| TenantId = BicepFunction.GetSubscription().TenantId | |
| }, |
aspire/src/Aspire.Hosting.Azure.Sql/AzureSqlExtensions.cs
Lines 284 to 294 in 73459e9
| internal static SqlServerAzureADAdministrator AddActiveDirectoryAdministrator(AzureResourceInfrastructure infra, SqlServer sqlServer, BicepValue<Guid> principalId, BicepValue<string> principalName) | |
| { | |
| var admin = new SqlServerAzureADAdministratorWorkaround($"{sqlServer.BicepIdentifier}_admin") | |
| { | |
| ParentOverride = sqlServer, | |
| LoginOverride = principalName, | |
| SidOverride = principalId | |
| }; | |
| infra.Add(admin); | |
| return admin; | |
| } |
Doing it this way means that the last managed identity / app that runs its roles bicep will be the admin, and any previous apps will no longer be able to talk to the SQL Server, since they no longer have access.
Instead of adding managed identities this way, we need to be adding the managed identity as a USER and assigning it a ROLE. See https://learn.microsoft.com/en-us/azure/azure-sql/database/authentication-aad-service-principal-tutorial?view=azuresql.
One idea on how to do this is to use a deployment script to run a SQL script that adds each app's managed identity as a USER and adds appropriate roles for that user.
This is also highly related to Azure SQL Server Provisioning Requires Admin (dotnet/aspire#8381). Maybe we try to solve them both together? We could grant the userPrincipalId (i.e. whoever is doing the deployment) as the SQL Server admin, and then run a deployment script for each app that needs to talk to the SQL Server.
Repro
var builder = DistributedApplication.CreateBuilder(args);
builder.AddAzureContainerAppEnvironment("env");
var dbServer = builder.AddAzureSqlServer("sqlserver")
.RunAsContainer(c => c.WithLifetime(ContainerLifetime.Persistent));
var todosDb = dbServer.AddDatabase("todosdb");
// The ApiDbService project is responsible for managing the database schema and seeding data.
var apiDbService = builder.AddProject<Projects.AspireStarterDb_ApiDbService>("apidbservice")
.WithReference(todosDb)
.WaitFor(todosDb)
.WithHttpsHealthCheck("/health")
.WithHttpsCommand("/reset-db", "Reset Database", iconName: "DatabaseLightning");
// The ApiService project provides backend HTTP APIs for the web frontend.
var apiService = builder.AddProject<Projects.AspireStarterDb_ApiService>("apiservice")
.WithReference(todosDb)
.WaitFor(apiDbService);$ azd up