Skip to content

Commit 299a666

Browse files
authored
feat: Add warnings for role assignment issues in RBAC checks (#7811)
1 parent ab84902 commit 299a666

2 files changed

Lines changed: 86 additions & 77 deletions

File tree

cli/azd/extensions/azure.ai.agents/internal/project/agent_identity_rbac.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@ package project
55

66
import (
77
"context"
8+
"errors"
89
"fmt"
910
"log"
1011
"maps"
12+
"net/http"
1113
"os"
1214
"slices"
1315
"strconv"
@@ -17,10 +19,12 @@ import (
1719

1820
"azureaiagent/internal/exterrors"
1921

22+
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
2023
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
2124
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthorization/v3"
2225
"github.com/azure/azure-dev/cli/azd/pkg/azdext"
2326
"github.com/azure/azure-dev/cli/azd/pkg/graphsdk"
27+
"github.com/azure/azure-dev/cli/azd/pkg/output"
2428
"github.com/google/uuid"
2529
)
2630

@@ -335,6 +339,31 @@ func ensureSingleAgentRBAC(
335339
armauthorization.PrincipalTypeServicePrincipal,
336340
)
337341
if err != nil {
342+
if respErr, ok := errors.AsType[*azcore.ResponseError](err); ok &&
343+
respErr.StatusCode == http.StatusForbidden {
344+
manualRemediationCommand := fmt.Sprintf(
345+
"az role assignment create --assignee-object-id %s --assignee-principal-type ServicePrincipal --role %s --scope %s",
346+
strconv.Quote(principalID),
347+
strconv.Quote("Azure AI User"),
348+
strconv.Quote(info.ProjectScope),
349+
)
350+
351+
// Write with warning color so it appears as a yellow warning, not a red error.
352+
fmt.Printf("%s\n", output.WithWarningFormat(
353+
"Could not assign 'Azure AI User' to agent identity '%s' (403 Forbidden).\n"+
354+
" The agent may not have access to the Foundry Project until this role is assigned.\n"+
355+
" Principal ID: %s\n"+
356+
" Foundry Project scope: %s\n"+
357+
" To remediate manually, run:\n"+
358+
" %s\n"+
359+
" Re-run after granting role assignment write, or set AZD_AGENT_SKIP_ROLE_ASSIGNMENTS=true.",
360+
agentName,
361+
principalID,
362+
info.ProjectScope,
363+
manualRemediationCommand,
364+
))
365+
return nil
366+
}
338367
return fmt.Errorf("failed to assign Azure AI User role: %w", err)
339368
}
340369

cli/azd/extensions/azure.ai.agents/internal/project/developer_rbac_check.go

Lines changed: 57 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,13 @@ import (
1010
"net/http"
1111
"strings"
1212

13-
"azureaiagent/internal/exterrors"
14-
1513
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
1614
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
1715
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthorization/v3"
1816
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerregistry/armcontainerregistry"
1917
"github.com/azure/azure-dev/cli/azd/pkg/azdext"
2018
"github.com/azure/azure-dev/cli/azd/pkg/graphsdk"
19+
"github.com/azure/azure-dev/cli/azd/pkg/output"
2120
)
2221

2322
const (
@@ -93,7 +92,8 @@ var sufficientRoleAssignWriteRoles = []string{
9392
// - Container Registry Tasks Contributor OR Container Registry Repository Contributor
9493
// on the ACR (to build images via remote build and push container images)
9594
//
96-
// Returns nil if all checks pass, or a structured error with suggestions on failure.
95+
// Missing roles are reported as warnings rather than errors so that deployment can proceed.
96+
// The developer may need to obtain the missing roles separately for full functionality.
9797
func CheckDeveloperRBAC(ctx context.Context, azdClient *azdext.AzdClient) error {
9898
azdEnvClient := azdClient.Environment()
9999
cEnvResponse, err := azdEnvClient.GetCurrent(ctx, &azdext.EmptyRequest{})
@@ -179,25 +179,21 @@ func CheckDeveloperRBAC(ctx context.Context, azdClient *azdext.AzdClient) error
179179
"Azure AI User → Foundry Project", info.ProjectScope,
180180
armauthorization.PrincipalTypeUser,
181181
); assignErr != nil {
182-
// Only treat 403 as a hard RBAC failure — transient errors (throttling, network) are non-blocking.
182+
// Warn rather than fail hard on 403 — deployment can proceed, but the developer
183+
// may not be able to interact with agents until this role is assigned.
183184
if respErr, ok := errors.AsType[*azcore.ResponseError](assignErr); ok &&
184185
respErr.StatusCode == http.StatusForbidden {
185-
return exterrors.Auth(
186-
exterrors.CodeDeveloperMissingAIUserRole,
187-
fmt.Sprintf(
188-
"your identity (%s) does not have the 'Azure AI User' role on the Foundry Project %s/%s "+
189-
"and auto-assign was denied: %s",
190-
userProfile.DisplayName, info.AccountName, info.ProjectName, assignErr,
191-
),
192-
fmt.Sprintf(
193-
"ask a subscription Owner or User Access Administrator to assign the 'Azure AI User' role "+
194-
"to your identity on the Foundry Project scope:\n"+
195-
" az role assignment create --assignee %s --role \"Azure AI User\" --scope %q",
196-
principalID, info.ProjectScope,
197-
),
198-
)
186+
fmt.Printf("%s\n", output.WithWarningFormat(
187+
"Your identity (%s) does not have the 'Azure AI User' role on the Foundry Project %s/%s "+
188+
"and auto-assign was denied.\n"+
189+
" Ask a subscription Owner or User Access Administrator to assign the role:\n"+
190+
" az role assignment create --assignee %s --role \"Azure AI User\" --scope %q",
191+
userProfile.DisplayName, info.AccountName, info.ProjectName,
192+
principalID, info.ProjectScope,
193+
))
194+
} else {
195+
fmt.Printf(" ⚠ Azure AI User auto-assign failed (non-auth error): %s — continuing\n", assignErr)
199196
}
200-
fmt.Printf(" ⚠ Azure AI User auto-assign failed (non-auth error): %s — continuing\n", assignErr)
201197
} else {
202198
fmt.Println(" ✓ Azure AI User auto-assigned to developer identity")
203199
}
@@ -212,29 +208,23 @@ func CheckDeveloperRBAC(ctx context.Context, azdClient *azdext.AzdClient) error
212208
if err != nil {
213209
fmt.Printf(" ⚠ Could not check role-assignment-write capability: %s\n", err)
214210
} else if !hasRoleWrite {
215-
return exterrors.Auth(
216-
exterrors.CodeDeveloperMissingRoleAssignWriteRole,
217-
fmt.Sprintf(
218-
"your identity (%s) does not have the permission to write role assignments on the "+
219-
"Foundry Project %s/%s — this is required for the postdeploy step to assign "+
220-
"'Azure AI User' to agent service principals",
221-
userProfile.DisplayName, info.AccountName, info.ProjectName,
222-
),
223-
fmt.Sprintf(
224-
"ask a subscription Owner or User Access Administrator to assign one of these roles "+
225-
"to your identity on the Foundry Project scope:\n"+
226-
" • Owner\n"+
227-
" • User Access Administrator\n"+
228-
" • Role Based Access Control Administrator\n"+
229-
" • Azure AI Project Manager\n"+
230-
" • Azure AI Account Owner\n\n"+
231-
" az role assignment create --assignee %s "+
232-
"--role \"Role Based Access Control Administrator\" --scope %q\n\n"+
233-
"Alternatively, if role assignments are managed externally:\n"+
234-
" AZD_AGENT_SKIP_ROLE_ASSIGNMENTS=true",
235-
principalID, info.ProjectScope,
236-
),
237-
)
211+
// Warn rather than fail hard: deployment can still proceed, but the postdeploy
212+
// step that assigns 'Azure AI User' to agent service principals may return 403.
213+
// Write with warning color so it appears as a yellow warning, not a red error.
214+
fmt.Printf("%s\n", output.WithWarningFormat(
215+
"Role assignment write not available on Foundry Project %s/%s.\n"+
216+
" The postdeploy step will attempt to assign 'Azure AI User' to agent service principals,\n"+
217+
" but may fail with a 403. To grant this permission, assign one of these roles:\n"+
218+
" • Owner\n"+
219+
" • User Access Administrator\n"+
220+
" • Role Based Access Control Administrator\n"+
221+
" • Azure AI Project Manager\n"+
222+
" • Azure AI Account Owner\n"+
223+
" az role assignment create --assignee %s "+
224+
"--role \"Role Based Access Control Administrator\" --scope %q\n"+
225+
" Or, if roles are managed externally: AZD_AGENT_SKIP_ROLE_ASSIGNMENTS=true",
226+
info.AccountName, info.ProjectName, principalID, info.ProjectScope,
227+
))
238228
} else {
239229
fmt.Println(" ✓ Role assignment write on Foundry Project")
240230
}
@@ -276,43 +266,33 @@ func CheckDeveloperRBAC(ctx context.Context, azdClient *azdext.AzdClient) error
276266
if !hasACRAccess {
277267
acrName := strings.TrimSuffix(normalizeLoginServer(acrEndpoint), ".azurecr.io")
278268
if isAbac {
279-
return exterrors.Auth(
280-
exterrors.CodeDeveloperMissingACRRole,
281-
fmt.Sprintf(
282-
"your identity (%s) does not have the required role on the ABAC-mode Container Registry '%s' "+
283-
"to push container images",
284-
userProfile.DisplayName, acrName,
285-
),
286-
fmt.Sprintf(
287-
"ask a subscription Owner or User Access Administrator to assign one of these roles "+
288-
"to your identity on the Container Registry scope:\n"+
289-
" • Owner (broad access)\n"+
290-
" • Container Registry Repository Writer (ABAC push)\n"+
291-
" • Container Registry Repository Contributor (superset of Writer)\n\n"+
292-
" az role assignment create --assignee %s "+
293-
"--role \"Container Registry Repository Writer\" --scope %q",
294-
principalID, acrResourceID,
295-
),
296-
)
297-
}
298-
return exterrors.Auth(
299-
exterrors.CodeDeveloperMissingACRRole,
300-
fmt.Sprintf(
301-
"your identity (%s) does not have the required role on the Container Registry '%s' "+
302-
"to build and push container images",
269+
fmt.Printf("%s\n", output.WithWarningFormat(
270+
"Your identity (%s) does not have the required role on the ABAC-mode Container Registry '%s' "+
271+
"to push container images.\n"+
272+
" Ask a subscription Owner or User Access Administrator to assign one of these roles:\n"+
273+
" • Owner (broad access)\n"+
274+
" • Container Registry Repository Writer (ABAC push)\n"+
275+
" • Container Registry Repository Contributor (superset of Writer)\n\n"+
276+
" az role assignment create --assignee %s "+
277+
"--role \"Container Registry Repository Writer\" --scope %q",
303278
userProfile.DisplayName, acrName,
304-
),
305-
fmt.Sprintf(
306-
"ask a subscription Owner or User Access Administrator to assign one of these roles "+
307-
"to your identity on the Container Registry scope:\n"+
308-
" • Owner or Contributor (broad access)\n"+
309-
" • AcrPush (push and pull images)\n"+
310-
" • Container Registry Tasks Contributor (remote build)\n"+
311-
" • Container Registry Repository Contributor (repository operations)\n\n"+
312-
" az role assignment create --assignee %s --role \"AcrPush\" --scope %q",
313279
principalID, acrResourceID,
314-
),
315-
)
280+
))
281+
} else {
282+
fmt.Printf("%s\n", output.WithWarningFormat(
283+
"Your identity (%s) does not have the required role on the Container Registry '%s' "+
284+
"to build and push container images.\n"+
285+
" Ask a subscription Owner or User Access Administrator to assign one of these roles:\n"+
286+
" • Owner or Contributor (broad access)\n"+
287+
" • AcrPush (push and pull images)\n"+
288+
" • Container Registry Tasks Contributor (remote build)\n"+
289+
" • Container Registry Repository Contributor (repository operations)\n\n"+
290+
" az role assignment create --assignee %s --role \"AcrPush\" --scope %q",
291+
userProfile.DisplayName, acrName,
292+
principalID, acrResourceID,
293+
))
294+
}
295+
return nil
316296
}
317297

318298
if isAbac {

0 commit comments

Comments
 (0)