functions/azure/Connect-Azure.ps1
# <copyright file="Connect-Azure.ps1" company="Endjin Limited"> # Copyright (c) Endjin Limited. All rights reserved. # </copyright> <# .SYNOPSIS Authenticates the current session to Azure via both Az PowerShell and the AzureCLI. .DESCRIPTION To help safeguard against running automation scripts against the incorrect Azure Tenant or Subscription this module requires an explicit connection to be setup. This function uses the provided details to ensure an authenticated session for both the Az PowerShell Cmdlets and the AzureCLI exists for the specified Tenant/Subscription. If not already authenticated, the function will use the 'AZURE_CLIENT_ID' and 'AZURE_CLIENT_SECRET' environment variables to connect as a service principal. Failing that, it attempts to detect when running interactively to support a manual login. NOTE: It is intended that other functions within this module that use either of these 2 tools must validate that a connection has been setup. .PARAMETER SubscriptionId The Azure Subscription that is the default target for any Azure operations. .PARAMETER AadTenantId The Azure Tenant that the Subscription belongs to. .PARAMETER SkipAzPowerShell When true, a connection to Azure via the Az PowerShell cmdlets will not be initialised. .PARAMETER SkipAzureCli When true, a connection to Azure via the AzureCLI will not be initialised. .PARAMETER TenantOnly When true, the connection will not be attached to a subscription. This is useful when working with identities that have no permissions to Azure resources (e.g. used only for Azure Active Directory automation). #> function Connect-Azure { [CmdletBinding(DefaultParameterSetName="Default")] param ( [Parameter(ParameterSetName="Default", Mandatory=$true)] [guid] $SubscriptionId, [Parameter(Mandatory=$true)] [guid] $AadTenantId, [switch] $SkipAzPowerShell, [switch] $SkipAzureCli, [Parameter(ParameterSetName="NoSubscriptions")] [switch] $TenantOnly ) # NOTE: This function is exempt from the test requiring consumers of AzPowerShell to call _EnsureAzureConnection $script:moduleContext.TenantOnly = $TenantOnly $script:moduleContext.SubscriptionId = $script:moduleContext.NoSubscriptions ? "" : $SubscriptionId $script:moduleContext.AadTenantId = $AadTenantId if ($script:moduleContext.TenantOnly) { Write-Host "Connecting with 'TenantOnly' option" } # Attempt to detect if we're running interactively or inside a build server $isInteractive = [Environment]::UserInteractive -and !(Test-Path env:\SYSTEM_TEAMFOUNDATIONSERVERURI) # Check whether the required environment variables are available to enable an auto-login with SP secret $requiredEnvVarsForAutoLogin = ( ![string]::IsNullOrEmpty($env:AZURE_CLIENT_ID) -and ` ![string]::IsNullOrEmpty($env:AZURE_CLIENT_SECRET) ) # Check whether the required environment variables are available to enable an auto-login with a managed identity $requiredEnvVarsForManagedIdLogin = ![string]::IsNullOrEmpty($env:AZURE_CLIENT_ID) if (-not $SkipAzPowerShell) { Write-Host "Validating Az PowerShell connection" $ctx = Get-AzContext if (!$ctx) { # Not currently connected, however the command supports attempting to login using convention-based # environment variables or using the Azure PowerShell's default interactive flow. $shouldAttemptLogin = $false # Setup common parameters for connecting to Azure $connectSplat = @{ TenantId = $AadTenantId } if (!$TenantOnly) { $connectSplat += @{ SubscriptionId = $SubscriptionId } } if ($requiredEnvVarsForAutoLogin) { # Setup parameters for a service principal login using the environment variables Write-Host "Not currently logged-in to Az PowerShell - attempting login via environment variables [ClientId=$env:AZURE_CLIENT_ID]" $userPassword = ConvertTo-SecureString -String $env:AZURE_CLIENT_SECRET -AsPlainText -Force $pscredential = New-Object -TypeName System.Management.Automation.PSCredential($env:AZURE_CLIENT_ID, $userPassword) $connectSplat += @{ ServicePrincipal = $true Credential = $pscredential } $shouldAttemptLogin = $true } elseif ($requiredEnvVarsForManagedIdLogin) { Write-Host "Not currently logged-in to Az PowerShell - attempting login via Managed Identity [ClientId=$env:AZURE_CLIENT_ID]" $connectSplat += @{ AccountId = $env:AZURE_CLIENT_ID Identity = $true } $shouldAttemptLogin = $true } elseif ($isInteractive) { # Fallback to attempting a manual login Write-Host "Not currently logged-in to Az PowerShell - triggering manual login" $shouldAttemptLogin = $true } # Attempt to login to Azure PowerShell if ($shouldAttemptLogin) { $connectWarns = $null $newCtx = Connect-AzAccount @connectSplat -WarningVariable connectWarns if (!$newCtx -and $connectWarns -match "Connect-AzAccount -UseDeviceAuthentication") { Write-Host "Falling-back to DeviceCode authentication flow" $newCtx = Connect-AzAccount @connectSplat -UseDeviceAuthentication } if (!$newCtx) { throw "Manual login to Azure PowerShell failed - check previous output." } } else { throw "Not currently connected to Azure PowerShell and unable to attempt an auto or manual login. For unattended scenarios set the 'AZURE_CLIENT_ID' and 'AZURE_CLIENT_SECRET' environment variables." } } elseif ($ctx -and ` $ctx.Tenant.Id -eq $AadTenantId -and ` (!$TenantOnly -and $ctx.Subscription.Id -ne $script:moduleContext.SubscriptionId) ) { # Try to switch to the required subscription, if we are connected to the right tenant. # This avoids an unnecessary validation failure when we're connected to the right tenant, # but not the intended subscription Write-Host "Switching to required subcription: $SubscriptionId" Set-AzContext -SubscriptionId $SubscriptionId | Out-Null } if (!(_ValidateAzureConnectionDetails -SubscriptionId $script:moduleContext.SubscriptionId -AadTenantId $AadTenantId -AzPowerShell -TenantOnly:$TenantOnly)) { Write-Error "The current Az PowerShell connection context does not match the specified details" } else { $script:moduleContext.AzPowerShell.Connected = $true } } if (-not $SkipAzureCli) { Write-Host "Validating AzureCLI connection" $splat = @{ AadTenantId = $AadTenantId } if ($TenantOnly) { $splat += @{ TenantOnly = $TenantOnly } } else { $splat += @{ SubscriptionId = $script:moduleContext.SubscriptionId } } Assert-AzCliLogin @splat | Out-Null if (!(_ValidateAzureConnectionDetails -SubscriptionId $script:moduleContext.SubscriptionId -AadTenantId $AadTenantId -AzureCli -TenantOnly:$TenantOnly)) { Write-Error "The current AzureCLI connection context does not match the specified details." } else { $script:moduleContext.AzureCli.Connected = $true } } } |