Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions cli/azd/extensions/azure.ai.agents/internal/cmd/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,12 +115,12 @@ func newInitCommand(rootFlags rootFlagsDefinition) *cobra.Command {
return fmt.Errorf("failed to ground into a project context: %w", err)
}

credential, err := azidentity.NewAzureDeveloperCLICredential(&azidentity.AzureDeveloperCLICredentialOptions{
TenantID: azureContext.Scope.TenantId,
AdditionallyAllowedTenants: []string{"*"},
credential, err := azure.NewCredential(ctx, azure.CredentialOptions{
TenantID: azureContext.Scope.TenantId,
SubscriptionID: azureContext.Scope.SubscriptionId,
})
if err != nil {
return fmt.Errorf("failed to create azure credential: %w", err)
return err
}

console := input.NewConsole(
Expand Down Expand Up @@ -316,12 +316,12 @@ func ensureEnvironment(ctx context.Context, flags *initFlags, azdClient *azdext.
return nil, fmt.Errorf("failed to get tenant ID: %w", err)
}

credential, err := azidentity.NewAzureDeveloperCLICredential(&azidentity.AzureDeveloperCLICredentialOptions{
TenantID: tenantResponse.TenantId,
AdditionallyAllowedTenants: []string{"*"},
credential, err := azure.NewCredential(ctx, azure.CredentialOptions{
TenantID: tenantResponse.TenantId,
SubscriptionID: foundryProject.SubscriptionId,
})
if err != nil {
return nil, fmt.Errorf("failed to create Azure credential: %w", err)
return nil, err
}

// Create Cognitive Services Projects client
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package azure

import (
"context"
"fmt"

"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
)

// CredentialScope defines the Azure resource scope for token validation.
type CredentialScope string

const (
// ScopeAIFoundry is the scope for Azure AI Foundry APIs.
ScopeAIFoundry CredentialScope = "https://ai.azure.com/.default"
// ScopeARM is the scope for Azure Resource Manager APIs.
ScopeARM CredentialScope = "https://management.azure.com/.default"
)

// CredentialOptions configures credential creation and validation.
type CredentialOptions struct {
// TenantID is the Azure AD tenant to authenticate against.
TenantID string
// SubscriptionID is used for error messages to help users identify the context.
SubscriptionID string
// Scope is the Azure resource scope to validate the credential against.
// If empty, defaults to ScopeARM.
Scope CredentialScope
}

// NewCredential creates an AzureDeveloperCLICredential and validates it can obtain a token.
// This catches multi-tenant authentication issues early with a helpful error message.
func NewCredential(ctx context.Context, options CredentialOptions) (*azidentity.AzureDeveloperCLICredential, error) {
cred, err := azidentity.NewAzureDeveloperCLICredential(&azidentity.AzureDeveloperCLICredentialOptions{
TenantID: options.TenantID,
AdditionallyAllowedTenants: []string{"*"},
})
if err != nil {
return nil, fmt.Errorf("failed to create Azure credential: %w", err)
}

scope := options.Scope
if scope == "" {
scope = ScopeARM
}

// Validate the credential by attempting to get a token.
// The token is cached by the SDK, so subsequent calls reuse it.
_, err = cred.GetToken(ctx, policy.TokenRequestOptions{
Scopes: []string{string(scope)},
})
if err != nil {
return nil, &AuthError{
SubscriptionID: options.SubscriptionID,
TenantID: options.TenantID,
Cause: err,
}
}

return cred, nil
}

// AuthError represents an authentication failure with context for helpful error messages.
type AuthError struct {
SubscriptionID string
TenantID string
Cause error
}

func (e *AuthError) Error() string {
return fmt.Sprintf(
"failed to authenticate for subscription '%s' in tenant '%s'.\n"+
"Suggestion: if you recently gained access to this subscription, re-run `azd auth login`. Otherwise, visit this subscription in Azure Portal, then run `azd auth login`",
e.SubscriptionID,
e.TenantID)
}

func (e *AuthError) Unwrap() error {
return e.Cause
}
17 changes: 9 additions & 8 deletions cli/azd/extensions/azure.ai.agents/internal/project/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package project

import (
"azureaiagent/internal/pkg/agents/agent_yaml"
"azureaiagent/internal/pkg/azure"
"bytes"
"context"
"encoding/json"
Expand Down Expand Up @@ -129,12 +130,12 @@ func (p *FoundryParser) SetIdentity(ctx context.Context, args *azdext.ProjectEve
return fmt.Errorf("failed to get tenant ID: %w", err)
}

cred, err := azidentity.NewAzureDeveloperCLICredential(&azidentity.AzureDeveloperCLICredentialOptions{
TenantID: tenantResponse.TenantId,
AdditionallyAllowedTenants: []string{"*"},
cred, err := azure.NewCredential(ctx, azure.CredentialOptions{
TenantID: tenantResponse.TenantId,
SubscriptionID: subscriptionID,
})
if err != nil {
return fmt.Errorf("failed to create Azure credential: %w", err)
return err
}

// Get Microsoft Foundry Project's managed identity
Expand Down Expand Up @@ -375,12 +376,12 @@ func (p *FoundryParser) CoboPostDeploy(ctx context.Context, args *azdext.Project
return fmt.Errorf("failed to get tenant ID: %w", err)
}

cred, err := azidentity.NewAzureDeveloperCLICredential(&azidentity.AzureDeveloperCLICredentialOptions{
TenantID: tenantResponse.TenantId,
AdditionallyAllowedTenants: []string{"*"},
cred, err := azure.NewCredential(ctx, azure.CredentialOptions{
TenantID: tenantResponse.TenantId,
SubscriptionID: projectSubscriptionID,
})
if err != nil {
return fmt.Errorf("failed to create Azure credential: %w", err)
return err
}

// Get Microsoft Foundry region using SDK
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,13 +97,14 @@ func (p *AgentServiceTargetProvider) Initialize(ctx context.Context, serviceConf
}
p.tenantId = tenantResponse.TenantId

// Create Azure credential
cred, err := azidentity.NewAzureDeveloperCLICredential(&azidentity.AzureDeveloperCLICredentialOptions{
TenantID: p.tenantId,
AdditionallyAllowedTenants: []string{"*"},
// Create and validate Azure credential
cred, err := azure.NewCredential(ctx, azure.CredentialOptions{
TenantID: p.tenantId,
SubscriptionID: subscriptionId,
Scope: azure.ScopeAIFoundry,
})
if err != nil {
return fmt.Errorf("failed to create Azure credential: %w", err)
return err
}
p.credential = cred

Expand Down
Loading