1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
|
function Login-AzSubscription {
<#
.SYNOPSIS
Log in to a predefined set of tenants+subscription combinations.
.DESCRIPTION
The sets define abstract subscription "shortnames" which will also
yield its tenant. To find out which just use tabulator expansion.
This function eases the login procedure. While Azure and AzureCLI
are still beta implementations, we need to work around a lot of
stuff, which is why this function has grown quite some in size.
Its usage will remain easy, however :-)
We expect ./private/vars.ps1 to be populated. It consists of:
- [Hashtable]$tenantMap = @{ subscrName = tenantUUID; ... }
- [Hashtable]$subscrMap = @{ subscrName = subscriptionUUID; ... }
(subscrName is a name chosen by you, it can be the actual name or
some mnemonic, whatever you prefer. The UUID(GUID) is the one in Azure.)
.EXAMPLE
Login-AzSubscription mysubscription
.PARAMETER subscrName
The subscription name. Its mapping has to exist in ./private/vars.ps1
(the module containing this function is delivered with an example).
#>
[Alias(
'Login-AzTenant',
'azlogin'
)]
Param (
[Parameter(
Mandatory=$true,
ValueFromPipeline=$true,
HelpMessage="The name (our alias) of the subscription you intend to login to.",
Position=0
)
]
[ValidateLength(1,64)]
[string]
# [azSubscriptions]
$subscrName
# $subscrEnum
)
# # Since PowerShell Enum doesn't give a fuck about sub-types, we better cast to string here.
# # Before, we tried to access $tenantMap[$subscrEnum] below and failed, so better keep it that way :-)
# $subscrName = [String]$subscrEnum
[regex]$uuidRex = '(?im)^[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}$'
if ( -not $tenantMap.Contains($subscrName) ) {
Write-Host "Exception. Did you populate /private/vars.ps1?"
throw [System.ArgumentException]::New("Known tenants do not include `"$($SubscrName)`"!")
}
if ( -not $subscrMap.Contains($subscrName) ) {
Write-Host "Exception. Did you populate /private/vars.ps1?"
throw [System.ArgumentException]::New("Known subscriptionss do not include `"$($SubscrName)`"!")
}
# PowerShell again: The below simple statement in any other language would work. Here you receive
# "System.Collections.Hashtable[learn]" instead of the value string assigned to key $TenantName:
# az login --tenant $tenantMap[$TenantName]
# ...here, we need an additional detour and assignment (WiP):
$tuuid = [String]$tenantMap[$subscrName]
if ( -not ($tuuid -match $uuidRex) ) {
# PowerShell again. Cannot just combine strings with strings directly (like "value of variable: ${variable}, right here").
# Most languages can. PS can't.
$throwstr = "Tenant ID string is not a UUID! ("
$throwstr += $tuuid
$throwstr += ")"
throw [System.ArgumentException]::New($throwstr)
}
$suuid = [String]$subscrMap[$subscrName]
if ( -not ($suuid -match $uuidRex) ) {
# PowerShell again. Cannot just combine strings with strings directly (like "value of variable: ${variable}, right here").
# Most languages can. PS can't.
$throwstr = "Subscription ID string is not a UUID! ("
$throwstr += $suuid
$throwstr += ")"
throw [System.ArgumentException]::New($throwstr)
}
$loggedout = $false
if ( ((az account list --only-show-errors -o json) | ConvertFrom-Json).Count -ne 0 ) {
Write-Host "Already logged in to an Azure subscription."
# For some very odd reason (bad coding in PS?* :-) ), this output would appear LAST. meaning after the last call of the function. (wtf?)
# * or do the round brackets do some kind of subshelling? other than what people told me? to be researched.
# (az account show --only-show-errors -o json | ConvertFrom-Json) | Select-Object tenantId,name
# ---
# Here's the thing with AzureCLI as a whole: Microsoft don't even adhere to their own frickin standards. If "az something"* fails, they won't throw an exception, and
# "az account" does not know about "-ErrorAction" (which is a de-facto standard!). Finally, PowerShell itself does not use error codes like everybody else, so you're stuck
# on some output to interpret. Morons. We hence just try to assign a variable to this command, and if the variable is empty, tadaaa error. Lel.
# * no, seriously, try "az something". "something" is unknown, and even then there is no darn exception**, just red text. No try/catch possible. Hilarious.
# ** Even better, "az something" does not complain about "-ErrorAction", it simply ignores it in this case. Absolute pros at work :D ("az account" at least states it does not adhere to that...)
$mytok = (az account get-access-token -o json --query '{expiresOn: expiresOn, expires_on: expires_on}' 2>$null | ConvertFrom-Json )
try {
$attempt = [string]$mytok['expiresOn']
Clear-Variable -name attempt
# Do not have sensitive data lingering about:
Clear-Variable mytok
# Set subscription:
Write-Host "Setting active subscription to $($suuid)...`n"
az account set -o jsonc --subscription $suuid
} catch {
[Console]::ForegroundColor = 'red'
Write-Host "Token possibly invalid, AzureCLI has no means of renewing tokens (sic),"
Write-Host "hence logging you out."
[Console]::ResetColor()
az logout
Write-Host "...done. Please log in again."
$loggedout = $true
}
} else {
$loginex = $("" | Out-String)
$loginex += "Not logged in, trying to log in.`n"
Write-Host $loginex
Write-Host "Tenant: $tuuid"
Write-Host "Subscription: `"$SubscrName`" = $tuuid"
if ( $tuuid -ne $null ) {
# TODO: research --use-device-code
# We cannot log in stating we are azure user user@whatnot, this would lead to not using MFA.
# Great design, Microsoft, I just want to enter my creds but not the frickin username every time. -.-'
# (Also, what about user-based vaulting (incl. an Enterprise Vault) and then using a TOTP?
# Microsoft won't even do a proper TOTP, they hide that behind their "Authenticator" (which is EFFING MANDATORY).
# Also, Microsoft take a huge dump on established standards like the aforementioned, or browser plugins for vaults.
# And many people still think this is good design. ಠ_ಠ (Do you?)
# )
# az login --username (Get-ConsolutAzLoginName) --tenant $tuuid
# ...so back to using that UI designed for noobs (its existence isn't bad - dictating it is, and makes devs more inefficient.)
# TODO: Can we work around with managed identities? And should we consider this at all, from a security perspective?
Write-Host "Logging in..."
az login -o jsonc --tenant $tuuid
Write-Host "...logged in."
Write-Host "Setting active subscription to $($suuid)..."
az account set -o jsonc --subscription $suuid
} else {
# PowerShell is just sitting on the zombie hydra that .NET is. --> https://powershellexplained.com/2017-04-07-all-dotnet-exception-list/#systemargumentnullexception
throw [System.ArgumentNullException]::New("UUID for tenant not found: $SubscrName")
}
}
if ( -not $loggedout ) {
Write-Host "Access token:"
az account get-access-token -o jsonc --query '{expiresOn: expiresOn, expires_on: expires_on, subscription: subscription, tenant: tenant, tokenType: tokenType}'
Write-Host "Active subscription:"
az account show -o jsonc --query '{name: name, id: id, tenantId: tenantId, user: user}'
}
}
|