Skip to content
Closed
3 changes: 2 additions & 1 deletion cli/azd/.vscode/cspell-azd-dictionary.txt
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ servicebus
setenvs
snapshotter
springapp
sqlcmd
sqlserver
sstore
staticcheck
Expand Down Expand Up @@ -216,4 +217,4 @@ webfrontend
westus2
wireinject
yacspin
zerr
zerr
2 changes: 2 additions & 0 deletions cli/azd/cmd/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ import (
"github.com/azure/azure-dev/cli/azd/pkg/tools/maven"
"github.com/azure/azure-dev/cli/azd/pkg/tools/npm"
"github.com/azure/azure-dev/cli/azd/pkg/tools/python"
"github.com/azure/azure-dev/cli/azd/pkg/tools/sqlcmd"
"github.com/azure/azure-dev/cli/azd/pkg/tools/swa"
"github.com/azure/azure-dev/cli/azd/pkg/workflow"
"github.com/mattn/go-colorable"
Expand Down Expand Up @@ -602,6 +603,7 @@ func registerCommonDependencies(container *ioc.NestedContainer) {
container.MustRegisterSingleton(dotnet.NewDotNetCli)
container.MustRegisterSingleton(git.NewGitCli)
container.MustRegisterSingleton(github.NewGitHubCli)
container.MustRegisterSingleton(sqlcmd.NewSqlCmdCli)
container.MustRegisterSingleton(javac.NewCli)
container.MustRegisterSingleton(kubectl.NewKubectl)
container.MustRegisterSingleton(maven.NewMavenCli)
Expand Down
3 changes: 3 additions & 0 deletions cli/azd/internal/tracing/events/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ const BicepInstallEvent = "tools.bicep.install"
// GitHubCliInstallEvent is the name of the event which tracks the overall GitHub cli install operation.
const GitHubCliInstallEvent = "tools.gh.install"

// SqlCmdCliInstallEvent is the name of the event which tracks the overall sqlcmd cli install operation.
const SqlCmdCliInstallEvent = "tools.sqlCmd.install"

// PackCliInstallEvent is the name of the event which tracks the overall pack cli install operation.
const PackCliInstallEvent = "tools.pack.install"

Expand Down
29 changes: 29 additions & 0 deletions cli/azd/pkg/auth/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,35 @@ func (m *Manager) GetLoggedInServicePrincipalTenantID(ctx context.Context) (*str
return currentUser.TenantID, nil
}

func (m *Manager) GetLoggedInServicePrincipalID(ctx context.Context) (*string, error) {
if m.UseExternalAuth() {
// When delegating to an external system, we have no way to determine what principal was used
return nil, nil
}

cfg, err := m.userConfigManager.Load()
if err != nil {
return nil, fmt.Errorf("fetching current user: %w", err)
}

if shouldUseLegacyAuth(cfg) {
// When delegating to az, we have no way to determine what principal was used
return nil, nil
}

authCfg, err := m.readAuthConfig()
if err != nil {
return nil, fmt.Errorf("fetching auth config: %w", err)
}

currentUser, err := readUserProperties(authCfg)
if err != nil {
return nil, ErrNoCurrentUser
}

return currentUser.ClientID, nil
}

func (m *Manager) newCredentialFromManagedIdentity(clientID string) (azcore.TokenCredential, error) {
options := &azidentity.ManagedIdentityCredentialOptions{}
if clientID != "" {
Expand Down
3 changes: 3 additions & 0 deletions cli/azd/pkg/azure/arm_template.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,9 @@ type AzdMetadataType string

const AzdMetadataTypeLocation AzdMetadataType = "location"
const AzdMetadataTypeGenerate AzdMetadataType = "generate"
const AzdMetadataTypePrincipalLogin AzdMetadataType = "principalLogin"
const AzdMetadataTypePrincipalId AzdMetadataType = "principalId"
const AzdMetadataTypePrincipalType AzdMetadataType = "principalType"
const AzdMetadataTypeGenerateOrManual AzdMetadataType = "generateOrManual"

type AzdMetadata struct {
Expand Down
47 changes: 47 additions & 0 deletions cli/azd/pkg/azureutil/principal.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package azureutil
import (
"context"
"fmt"
"log"

"github.com/azure/azure-dev/cli/azd/pkg/auth"
"github.com/azure/azure-dev/cli/azd/pkg/tools/azcli"
Expand Down Expand Up @@ -34,3 +35,49 @@ func GetCurrentPrincipalId(ctx context.Context, userProfile *azcli.UserProfileSe

return oid, nil
}

type LoggedInPrincipalProfileData struct {
PrincipalId string
PrincipalType string
PrincipalLoginName string
}

// LoggedInPrincipalProfile returns the info about the current logged in principal
func LoggedInPrincipalProfile(
ctx context.Context, userProfile *azcli.UserProfileService, tenantId string) (*LoggedInPrincipalProfileData, error) {
principalProfile, err := userProfile.SignedProfile(ctx, tenantId)
if err == nil {
return &LoggedInPrincipalProfileData{
PrincipalId: principalProfile.Id,
PrincipalType: "User",
PrincipalLoginName: principalProfile.UserPrincipalName,
}, nil
}

token, err := userProfile.GetAccessToken(ctx, tenantId)
if err != nil {
return nil, fmt.Errorf("getting access token: %w", err)
}

tokenClaims, err := auth.GetClaimsFromAccessToken(token.AccessToken)
if err != nil {
return nil, fmt.Errorf("getting oid from token: %w", err)
}

appProfile, err := userProfile.AppProfile(ctx, tenantId)
if err == nil {
return &LoggedInPrincipalProfileData{
PrincipalId: *appProfile.AppId,
PrincipalType: "Application",
PrincipalLoginName: appProfile.DisplayName,
}, nil
} else {
log.Println(fmt.Errorf("fetching current user information: %w", err))
}

return &LoggedInPrincipalProfileData{
PrincipalId: tokenClaims.LocalAccountId(),
PrincipalType: "User",
PrincipalLoginName: tokenClaims.Email,
}, nil
}
60 changes: 47 additions & 13 deletions cli/azd/pkg/infra/provisioning/bicep/bicep_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -1967,6 +1967,10 @@ func (p *BicepProvider) ensureParameters(
key string
param azure.ArmTemplateParameterDefinition
}
currentPrincipalProfile, err := p.curPrincipal.CurrentPrincipalProfile(ctx)
if err != nil {
return nil, fmt.Errorf("fetching current principal profile: %w", err)
}

for _, key := range sortedKeys {
param := template.Parameters[key]
Expand Down Expand Up @@ -2009,20 +2013,50 @@ func (p *BicepProvider) ensureParameters(
// If the parameter is tagged with {type: "generate"}, skip prompting.
// We generate it once, then save to config for next attempts.`.
azdMetadata, hasMetadata := param.AzdMetadata()
if hasMetadata && parameterType == ParameterTypeString && azdMetadata.Type != nil &&
*azdMetadata.Type == azure.AzdMetadataTypeGenerate {

// - generate once
genValue, err := autoGenerate(key, azdMetadata)
if err != nil {
return nil, err
}
configuredParameters[key] = azure.ArmParameterValue{
Value: genValue,
if hasMetadata && parameterType == ParameterTypeString && azdMetadata.Type != nil {
azdMetadataType := *azdMetadata.Type
switch azdMetadataType {
case azure.AzdMetadataTypeGenerate:
// - generate once
genValue, err := autoGenerate(key, azdMetadata)
if err != nil {
return nil, err
}
configuredParameters[key] = azure.ArmParameterValue{
Value: genValue,
}
mustSetParamAsConfig(key, genValue, p.env.Config, param.Secure())
configModified = true
continue
// Check metadata for auto-inject values [principalId, principalType, principalLogin]
case azure.AzdMetadataTypePrincipalLogin:
pLogin := currentPrincipalProfile.PrincipalLoginName
configuredParameters[key] = azure.ArmParameterValue{
Value: pLogin,
}
mustSetParamAsConfig(key, pLogin, p.env.Config, param.Secure())
configModified = true
continue
case azure.AzdMetadataTypePrincipalId:
pLogin := currentPrincipalProfile.PrincipalId
configuredParameters[key] = azure.ArmParameterValue{
Value: pLogin,
}
mustSetParamAsConfig(key, pLogin, p.env.Config, param.Secure())
configModified = true
continue
case azure.AzdMetadataTypePrincipalType:
pLogin := currentPrincipalProfile.PrincipalType
configuredParameters[key] = azure.ArmParameterValue{
Value: pLogin,
}
mustSetParamAsConfig(key, pLogin, p.env.Config, param.Secure())
configModified = true
continue
default:
// Do nothing
log.Println("Skipping actions for azd unknown metadata bicep parameter with type: ", azdMetadataType)
}
mustSetParamAsConfig(key, genValue, p.env.Config, param.Secure())
configModified = true
continue
}

// No saved value for this required parameter, we'll need to prompt for it.
Expand Down
7 changes: 7 additions & 0 deletions cli/azd/pkg/infra/provisioning/bicep/prompt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

"github.com/azure/azure-dev/cli/azd/pkg/account"
"github.com/azure/azure-dev/cli/azd/pkg/azure"
"github.com/azure/azure-dev/cli/azd/pkg/azureutil"
"github.com/azure/azure-dev/cli/azd/pkg/cloud"
"github.com/azure/azure-dev/cli/azd/pkg/convert"
"github.com/azure/azure-dev/cli/azd/pkg/environment"
Expand Down Expand Up @@ -320,6 +321,12 @@ func TestPromptForParametersLocation(t *testing.T) {

type mockCurrentPrincipal struct{}

// CurrentPrincipalProfile implements provisioning.CurrentPrincipalIdProvider.
func (m *mockCurrentPrincipal) CurrentPrincipalProfile(
ctx context.Context) (*azureutil.LoggedInPrincipalProfileData, error) {
return &azureutil.LoggedInPrincipalProfileData{}, nil
}

func (m *mockCurrentPrincipal) CurrentPrincipalId(_ context.Context) (string, error) {
return "11111111-1111-1111-1111-111111111111", nil
}
15 changes: 15 additions & 0 deletions cli/azd/pkg/infra/provisioning/current_principal_id_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ type CurrentPrincipalIdProvider interface {
// CurrentPrincipalId returns the object id of the current logged in principal, or an error if it can not be
// determined.
CurrentPrincipalId(ctx context.Context) (string, error)
CurrentPrincipalProfile(ctx context.Context) (*azureutil.LoggedInPrincipalProfileData, error)
}

func NewPrincipalIdProvider(
Expand Down Expand Up @@ -47,3 +48,17 @@ func (p *principalIDProvider) CurrentPrincipalId(ctx context.Context) (string, e

return principalId, nil
}

func (p *principalIDProvider) CurrentPrincipalProfile(ctx context.Context) (*azureutil.LoggedInPrincipalProfileData, error) {
tenantId, err := p.subResolver.LookupTenant(ctx, p.env.GetSubscriptionId())
if err != nil {
return nil, fmt.Errorf("getting tenant id for subscription %s. Error: %w", p.env.GetSubscriptionId(), err)
}

principalProfile, err := azureutil.LoggedInPrincipalProfile(ctx, p.userProfileService, tenantId)
if err != nil {
return nil, fmt.Errorf("fetching current user information: %w", err)
}

return principalProfile, nil
}
Loading