aspire deploy does not populate KnownParameters.UserPrincipalId for individual Bicep modules
- Dominant language
- C#
- Stars
- 6.3k
- Forks
- 991
- Avg merge
- 2d 12h
- Merged PRs (30d)
- 201
Description
### Is there an existing issue for this?
- [x] I have searched the existing issues
### Describe the bug
We deploy a containerized application from GitHub Actions with `aspire deploy`.
Aspire provisions the Azure Container Registry, then the deployment pipeline
pushes the application image to it. The GitHub OIDC service principal therefore
needs `AcrPush`; the separate managed identity used by the Container App needs
only the `AcrPull` assignment that Aspire already provisions.
Because Aspire creates the registry during the first deployment, we cannot
grant the deployment principal access to it during an earlier identity-setup
step. We want the AppHost to create a registry-scoped `AcrPush` assignment
alongside the generated registry, keeping the deployment model code-first and
least-privileged.
The ACR documentation recommends giving an automated pipeline's service
principal or managed identity `AcrPush`, and `ConfigureInfrastructure` is the
documented extension point for customizations beyond the built-in APIs. We use
that extension point with `KnownParameters.UserPrincipalId`, whose API
documentation identifies it as the deployment principal.
`aspire publish` generates a valid root template for this model, but
`aspire deploy` provisions the ACR module without a value for
`userPrincipalId`. ARM rejects the module before creating the registry.
Without a supported way to express this assignment, the first deployment
requires an out-of-band or two-phase provisioning path for a resource that
Aspire otherwise owns.
### Expected Behavior
`aspire deploy` can provision this registry-scoped assignment in the same pass
that creates the registry. If `KnownParameters.UserPrincipalId` is not intended
for this scenario, the deployment path should provide or document the supported
alternative.
### Steps To Reproduce
Save this single-file AppHost as `apphost.cs`:
```csharp
#:sdk Aspire.AppHost.Sdk@13.5.2
#:package Aspire.Hosting.Azure.AppContainers@13.5.2
using Aspire.Hosting.Azure;
using Azure.Provisioning;
using Azure.Provisioning.Authorization;
using Azure.Provisioning.ContainerRegistry;
using Azure.Provisioning.Expressions;
var builder = DistributedApplication.CreateBuilder(args);
var environment = builder.AddAzureContainerAppEnvironment("environment");
environment
.GetAzureContainerRegistry()
.ConfigureInfrastructure(infrastructure =>
{
var registry = infrastructure
.GetProvisionableResources()
.OfType()
.Single();
var principalId = new ProvisioningParameter(
AzureBicepResource.KnownParameters.UserPrincipalId,
typeof(Guid));
infrastructure.Add(principalId);
var assignment = registry.CreateRoleAssignment(
ContainerRegistryBuiltInRole.AcrPush,
RoleManagementPrincipalType.ServicePrincipal,
principalId);
assignment.Name = BicepFunction.CreateGuid(
registry.Id,
principalId,
assignment.RoleDefinitionId);
infrastructure.Add(assignment);
});
builder.Build().Run();
```
After signing in with the Azure CLI, select a disposable resource group:
```powershell
$env:Azure__SubscriptionId = az account show --query id --output tsv
$env:Azure__Location = "westus3"
$env:Azure__ResourceGroup = "aspire-user-principal-repro"
dnx Aspire.Cli@13.5.2 -y -- do provision-environment-acr `
--apphost .\apphost.cs `
--environment repro `
--non-interactive `
--pipeline-log-level debug `
--include-exception-details
```
`aspire deploy` includes `provision-environment-acr`; `aspire do` targets that
step and its dependencies without attempting unrelated resources.
For comparison, `aspire publish` succeeds:
```powershell
dnx Aspire.Cli@13.5.2 -y -- publish `
--apphost .\apphost.cs `
--output-path .\output `
--environment repro `
--non-interactive
```
The generated `main.bicep` passes `userPrincipalId: principalId`, while
`environment-acr.bicep` declares `userPrincipalId` without a default.
### Exceptions (if any)
```text
Aspire.Hosting.Azure.ProvisioningFailedException:
Deployment failed: Error code = InvalidTemplate, Message = Deployment template
validation failed: 'The value for the template parameter 'userPrincipalId' at
line '19' and column '24' is not provided.'
Status: 400 (Bad Request)
ErrorCode: InvalidTemplate
Path: properties.template.parameters.userPrincipalId
at Aspire.Hosting.Azure.Provisioning.BicepProvisioner.GetOrCreateResourceAsync(...)
```
### Aspire doctor output
```text
Aspire Environment Check
========================
Aspire
Aspire CLI version 13.5.2
Developer Control Plane (DCP) connection health checks succeeded
AppHost
AppHost version 13.5.2 (apphost.cs)
.NET SDK
.NET 10.0.303 installed (x64)
Container Runtime
Docker v29.6.1: running
Environment
Operating system: Windows 10.0.26200.0
Summary: 6 passed, 3 unrelated development-certificate warnings, 0 failed
```
### Anything else?
#### Verified workaround
We can resolve the GitHub OIDC service principal's Azure **object ID** before
starting Aspire and provide it explicitly through AppHost configuration:
```csharp
var deploymentPrincipalId = builder.Configuration["DeploymentPrincipalId"];
if (!Guid.TryParseExact(deploymentPrincipalId, "D", out _))
{
throw new InvalidOperationException(
"DeploymentPrincipalId must contain the deployment principal's object ID.");
}
var principalId = new ProvisioningParameter(
AzureBicepResource.KnownParameters.UserPrincipalId,
typeof(string))
{
Value = new BicepValue(deploymentPrincipalId),
};
infrastructure.Add(principalId);
```
The input must be the service principal's object ID, not its application/client
ID. The object ID is an identifier rather than a secret.
We tested this workaround against a disposable Azure resource group. Aspire
successfully provisioned one ACR and exactly one registry-scoped `AcrPush`
assignment whose principal ID and type matched the GitHub OIDC service
principal.
Using `KnownParameters.PrincipalId` without an explicit value does not work
around the problem. `aspire deploy` instead fails in
`PopulateWellKnownParameters` with:
```text
An Azure principal parameter was not supplied a value. Ensure you are using an
environment that supports role assignments, for example
AddAzureContainerAppEnvironment.
```
Is explicitly supplying the service principal object ID the recommended way to
model this assignment, or is there an Aspire API intended to grant a role to the
deployment principal? If this customization is not intended, guidance on the
supported bootstrap flow would help.
#### Additional context
- Azure CLI 2.84.0
- The original failure occurred under GitHub Actions on Ubuntu using an OIDC
service principal. The single-file reproduction produces the same ARM error
with a local Azure CLI user.
- The generated ACR module from the single-file reproduction is identical to
the original application's module after normalizing only the resource name.
- The implementation appears asymmetric: `AzurePublishingContext` handles
`KnownParameters.UserPrincipalId`, while
`BicepProvisioner.PopulateWellKnownParameters` does not:
https://github.com/microsoft/aspire/blob/v13.5.2/src/Aspire.Hosting.Azure/AzurePublishingContext.cs
https://github.com/microsoft/aspire/blob/v13.5.2/src/Aspire.Hosting.Azure/Provisioning/Provisioners/BicepProvisioner.cs
- Individual-module deployment was introduced by
https://github.com/microsoft/aspire/pull/11098.
- https://github.com/microsoft/aspire/pull/18063 changes principal-type
resolution in the same method but does not handle `UserPrincipalId`.
Contributor guide
Research direction
Start by comparing AzurePublishingContext.cs with Provisioning/Provisioners/BicepProvisioner.cs, especially PopulateWellKnownParameters, and review the individual-module deployment introduced by pull request 11098. Run the single-file apphost.cs reproduction with the documented aspire do provision-environment-acr command; done means aspire deploy provisions the ACR role assignment with userPrincipalId in the same pass without the ARM validation error.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- azure, csharp
- Domain
- cloud, devops, tooling
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 68/100