-
Notifications
You must be signed in to change notification settings - Fork 34
/
AksEdgeAzureSetup.ps1
369 lines (356 loc) · 16.5 KB
/
AksEdgeAzureSetup.ps1
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
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
<#
Sample script to setup Azure subscription for Arc for Kubernetes Connection
#>
Param(
[String]$jsonFile,
[switch]$spContributorRole,
[switch]$spCredReset
)
#Requires -RunAsAdministrator
New-Variable -Name gAksEdgeAzureSetup -Value "1.0.230829.1100" -Option Constant -ErrorAction SilentlyContinue
New-Variable -Option Constant -ErrorAction SilentlyContinue -Name cliMinVersions -Value @{
"azure-cli" = "2.41.0"
"azure-cli-core" = "2.41.0"
}
New-Variable -Option Constant -ErrorAction SilentlyContinue -Name arcLocations -Value @(
"westeurope", "eastus", "westcentralus", "southcentralus", "southeastasia", "uksouth",
"eastus2", "westus2", "australiaeast", "northeurope", "francecentral", "centralus",
"westus", "northcentralus", "koreacentral", "japaneast", "eastasia", "westus3",
"canadacentral", "eastus2euap"
)
function Test-AzVersions {
#Function to check if the installed az versions are greater or equal to minVersions
$retval = $true
$curVersion = (az version) | ConvertFrom-Json
if (-not $curVersion) { return $false }
foreach ($item in $cliMinVersions.Keys ) {
Write-Host " Checking $item minVersion $($cliMinVersions.$item).." -NoNewline
$fgcolor = 'Green'
if ($curVersion.$item) {
Write-Verbose " Comparing $($curVersion.$item) -lt $($cliMinVersions.$item)."
if ([version]$($curVersion.$item) -lt [version]$($cliMinVersions.$item)) {
$retval = $false
$fgcolor = 'Red'
}
Write-Host "found $($curVersion.$item)" -ForegroundColor $fgcolor
}
}
return $retval
}
function Install-AzCli {
#Check if Az CLI is installed. If not install it.
$AzCommand = Get-Command -Name az -ErrorAction SilentlyContinue
if (!$AzCommand) {
$CLIPath = "C:\Program Files (x86)\Microsoft SDKs\Azure\CLI2\wbin"
Write-Host "> Installing AzCLI..."
Push-Location $env:TEMP
$progressPreference = 'silentlyContinue'
Invoke-WebRequest -Uri https://aka.ms/installazurecliwindows -OutFile .\AzureCLI.msi
$progressPreference = 'Continue'
Start-Process msiexec.exe -Wait -ArgumentList '/I AzureCLI.msi /passive'
Remove-Item .\AzureCLI.msi
Pop-Location
[System.Environment]::SetEnvironmentVariable("Path", "$($CLIPath);$env:Path")
az config set core.disable_confirm_prompt=yes
az config set core.only_show_errors=yes
#az config set auto-upgrade.enable=yes
}
Write-Host "> Azure CLI installed" -ForegroundColor Green
if (-not (Test-AzVersions)) {
Write-Host "> Required Az versions are not installed. Attempting az upgrade. This may take a while."
az upgrade --all --yes
if (-not (Test-AzVersions)) {
Write-Host "Error: Required versions not found after az upgrade. Please try uninstalling and reinstalling" -ForegroundColor Red
}
}
}
# Formats JSON in a nicer format than the built-in ConvertTo-Json does.
# https://github.com/PowerShell/PowerShell/issues/2736
function Format-Json([Parameter(Mandatory, ValueFromPipeline)][String] $json) {
$indent = 0;
($json -Split '\n' |
ForEach-Object {
if ($_ -match '[\}\]]') {
# This line contains ] or }, decrement the indentation level
$indent--
}
$line = (' ' * $indent * 2) + $_.TrimStart().Replace(': ', ': ')
if ($_ -match '[\{\[]') {
# This line contains [ or {, increment the indentation level
$indent++
}
$line
}) -Join "`n"
}
function AssignRole([String] $roleToAssign) {
#NOTE: using global values here for rest of the parameters.
$roleparams = @(
"--assignee", "$($servicePrincipal.appId)",
"--role", "$roleToAssign",
"--scope", "$rguri"
)
Write-Host "Creating $roleToAssign role assignment"
$res = (az role assignment create @roleparams ) | ConvertFrom-Json
if (!$res) { Write-Host " Error in assigning $roleToAssign role " -ForegroundColor Red }
}
###
# Main
###
Write-Host "AksEdgeAzureSetup version `t: $gAksEdgeAzureSetup"
if (-not $jsonFile) {
$jsonFile = "$PSScriptRoot\AzureConfig.json"
}
if (-not(Test-Path -Path "$jsonFile" -PathType Leaf)) {
Write-Host "Error: Incorrect input. Enter valid jsonFile path" -ForegroundColor Red
exit -1
}
Write-Verbose "Loading $jsonFile.."
$jsonContent = Get-Content "$jsonFile" | ConvertFrom-Json
if ($jsonContent.Azure) {
$aicfg = $jsonContent.Azure
} elseif ($jsonContent.SubscriptionId) {
$aicfg = $jsonContent
} else {
Write-Host "Error: Incorrect json content" -ForegroundColor Red
exit -1
}
if ($arcLocations -inotcontains $($aicfg.Location)) {
Write-Host "Error: Location $($aicfg.Location) is not supported for Azure Arc" -ForegroundColor Red
Write-Host "Supported Locations : $arcLocations"
exit -1
}
# Install Cli
Install-AzCli
Write-Host "$aicfg"
Write-Host "> az login to create/update service principal" -ForegroundColor Cyan
$loginparams = @("--scope", "https://graph.microsoft.com//.default" )
if ($($aicfg.TenantId)) {
$loginparams += @("--tenant", $($aicfg.TenantId))
}
$session = (az login @loginparams) | ConvertFrom-Json
if (-not $session) {
Write-Host "Error: Login failed. See error above and if required specify the tenantId in the input json file." -ForegroundColor Red
exit -1
}
if ($($aicfg.SubscriptionId)) {
#If SubscriptionId is specified, look for that in the session
$reqSession = $session | Where-Object { ($_.id -eq $aicfg.SubscriptionId) -and ($_.state -eq 'Enabled') }
if (!$reqSession) {
Write-Host "Error: [$($aicfg.SubscriptionId)] not found or not enabled." -ForegroundColor Red
Write-Host "Available subscription ids with state :" -ForegroundColor Cyan
$subinfo = $session | Select-Object name, id, state
Write-Host ($subinfo | Out-String)
#Write-Host ($($session.id) -join "`n") -ForegroundColor Cyan
az logout
exit -1
}
(az account set --subscription $($aicfg.SubscriptionId)) | Out-Null
} elseif ($($aicfg.SubscriptionName)) {
#If SubscriptionName is specified, look for that in the session
$reqSession = $session | Where-Object { ($_.name -eq $aicfg.SubscriptionName) -and ($_.state -eq 'Enabled') }
if (!$reqSession) {
Write-Host "Error: [$($aicfg.SubscriptionName)] not found or not enabled." -ForegroundColor Red
Write-Host "Available subscription names with state :" -ForegroundColor Cyan
$subinfo = $session | Select-Object name, id, state
Write-Host ($subinfo | Out-String)
az logout
exit -1
}
(az account set --subscription $($reqSession.id)) | Out-Null
} else {
#nothing specified. So use the default subscription and continue
if ($session.Count -gt 1) {
Write-Host ">>> Multiple subscriptions found :"
$subinfo = $session | Select-Object name, id , state
Write-Host ($subinfo | Out-String)
$sub = $session | Where-Object { $_.IsDefault -eq $true }
} else { $sub = $session }
Write-Host ">>> Default subscription is $($sub.name)[$($sub.id)]" -ForegroundColor Cyan
}
$session = (az account show | ConvertFrom-Json -ErrorAction SilentlyContinue)
$aicfg.SubscriptionId = $session.id
$aicfg.SubscriptionName = $session.name
$aicfg.TenantId = $session.tenantId
Write-Host "Logged in $($session.name) subscription as $($session.user.name) ($($session.user.type))" -ForegroundColor Cyan
Write-Host "TenantID : $($aicfg.TenantId)" -ForegroundColor Cyan
Write-Host "SubscriptionId : $($aicfg.SubscriptionId)" -ForegroundColor Cyan
$hasRights = $false
$userinfo = (az ad signed-in-user show) | ConvertFrom-Json
Write-Host "User Principal Name : $($userinfo.userPrincipalName)"
Write-Host "Looking for Azure RBAC roles"
$adminroles = (az role assignment list --all --assignee $userinfo.userPrincipalName --include-inherited) | ConvertFrom-Json
if ($adminroles) {
Write-Host "Roles enabled for this account are:" -ForegroundColor Cyan
foreach ($role in $adminroles) {
Write-Host "$($role.roleDefinitionName) for scope $($role.scope)" -ForegroundColor Cyan
if (($($role.scope) -eq "/subscriptions/$($aicfg.SubscriptionId)") -and ($role.roleDefinitionName -match 'Owner')) {
Write-Host "* You have sufficient privileges" -ForegroundColor Green
$hasRights = $true
}
}
}
if (-not $hasRights) {
# two stage call to work around issue reported here : https://github.com/Azure/azure-powershell/issues/15261 which occurs for CSP subscriptions
# look for classic administrators only when there is no Azure RBAC roles defined
Write-Host "Looking for classic administrator roles"
$adminroles = (az role assignment list --include-classic-administrators) | ConvertFrom-Json
$adminrole = $adminroles | Where-Object { $_.principalName -ieq $($session.user.name) }
if ($adminrole) {
Write-Host "Roles enabled for this account are:" -ForegroundColor Cyan
foreach ($role in $adminrole) {
Write-Host "$($role.roleDefinitionName) for scope $($role.scope)" -ForegroundColor Cyan
if (($($role.scope) -eq "/subscriptions/$($aicfg.SubscriptionId)") -and (( $role.roleDefinitionName -match 'Administrator'))) {
Write-Host "* You have sufficient privileges" -ForegroundColor Green
$hasRights = $true
}
}
}
}
if (-not $hasRights) {
Write-Host "Error: You do not have sufficient privileges for this subscription $($aicfg.SubscriptionId). Please refer to 'https://learn.microsoft.com/en-us/azure/role-based-access-control/role-assignments-steps#privileged-administrator-roles' for more details." -ForegroundColor Red
az logout
exit -1
}
# Resource group
$rgname = $aicfg.ResourceGroupName
$rguri = "/subscriptions/$($aicfg.SubscriptionId)/resourceGroups/$rgname"
Write-Host "Checking $rgname..."
$rgexists = az group exists --name $rgname
if ($rgexists -ieq 'true') {
Write-Host "* $rgname exists" -ForegroundColor Green
} else {
Write-Host "Creating $rgname resource group"
$rg = (az group create --resource-group $rgname -l $aicfg.Location | ConvertFrom-Json -ErrorAction SilentlyContinue)
if ($rg) {
Write-Host "$($rg.name) resource group created" -ForegroundColor Green
} else {
Write-Host "Error: Failed to create $rgname resource group" -ForegroundColor Red
az logout
exit -1
}
}
# Check and enable namespaces
$namespaces = @("Microsoft.HybridCompute", "Microsoft.GuestConfiguration", "Microsoft.HybridConnectivity",
"Microsoft.Kubernetes", "Microsoft.KubernetesConfiguration", "Microsoft.ExtendedLocation")
foreach ($namespace in $namespaces) {
Write-Host "Checking $namespace..."
$provider = (az provider show -n $namespace | ConvertFrom-Json -ErrorAction SilentlyContinue)
if ($provider.registrationState -ieq "Registered") {
Write-Host "* $namespace provider registered" -ForegroundColor Green
} else {
Write-Host "Registering $namespace provider. This can take some time. Please wait..." -ForegroundColor Yellow
$provider = (az provider register -n $namespace --wait | ConvertFrom-Json -ErrorAction SilentlyContinue)
Write-Host "$namespace provider registered successfully." -ForegroundColor Green
}
}
# Create Service Principal
$spName = $aicfg.ServicePrincipalName
$spApp = (az ad sp list --display-name $spName | ConvertFrom-Json -ErrorAction SilentlyContinue)
$servicePrincipal = $null
$enableContributor = $spContributorRole.IsPresent
$enableKcOnboarding = (!$spContributorRole.IsPresent)
$enableAcmOnboarding = (!$spContributorRole.IsPresent)
$savePassword = $false
if ($spApp -is [Array]) {$spApp = $spApp | Where-Object {$_.displayName -ieq $spName}; }
if ($spApp) {
# service principal found. Check roles required
$servicePrincipal = $spApp
Write-Host "$spName is already present."
$spRoles = (az role assignment list --all --assignee $($spApp.appId)) | ConvertFrom-Json
if ($spRoles) {
$spRolesRgScope = $spRoles | Where-Object {$_.scope -eq $rguri } # resource group scope
if ($spRolesRgScope) {
if ($spRolesRgScope.roleDefinitionName -contains 'Contributor') {
Write-Host "* Contributor role enabled" -ForegroundColor Green
$enableContributor = $false
$enableKcOnboarding = $false
$enableAcmOnboarding = $false
}
if ($spRolesRgScope.roleDefinitionName -contains 'Azure Connected Machine Onboarding') {
Write-Host "* Azure Connected Machine Onboarding role enabled" -ForegroundColor Green
$enableAcmOnboarding = $false
}
if ($spRolesRgScope.roleDefinitionName -contains 'Kubernetes Cluster - Azure Arc Onboarding') {
Write-Host "* Kubernetes Cluster - Azure Arc Onboarding role enabled" -ForegroundColor Green
$enableKcOnboarding = $false
}
}
}
#TODO : Check assigning multiple roles in one go.
if ($enableContributor) {
AssignRole -roleToAssign "Contributor"
} elseif ($enableAcmOnboarding) {
#Check and assign the connected machine onboarding role. the kuberenetes role is assigned later.
AssignRole -roleToAssign "Azure Connected Machine Onboarding"
}
if ($spCredReset) {
Write-Host "Resetting credentials.."
$servicePrincipal = (az ad sp credential reset --id $spApp.appId | ConvertFrom-Json)
if ($servicePrincipal) {
Write-Host "ServicePrincipal credentials reset successfully"
$savePassword = $true
} else {
Write-Host "ServicePrincipal reset failed"
az logout
exit -1
}
}
} else {
Write-Host "$spName not found. Creating.."
$spparams = @(
"--name", "$spName",
"--scopes", "$rguri"
)
if ($spContributorRole) {
$spparams += @("--role", "Contributor")
} else {
$spparams += @("--role", "Azure Connected Machine Onboarding")
}
$servicePrincipal = (az ad sp create-for-RBAC @spparams | ConvertFrom-Json)
if (!$servicePrincipal) {
Write-Host "Error: ServicePrincipal creation failed" -ForegroundColor Red
az logout
exit -1
}
$savePassword = $true
}
if ($enableKcOnboarding) {
#Assign the Kubernetes Cluster - Azure Arc Onboarding role to serviceprincipal too
AssignRole -roleToAssign "Kubernetes Cluster - Azure Arc Onboarding"
}
if ($savePassword) {
$aicfg | Add-Member -MemberType NoteProperty -Name 'Auth' -Value @{"ServicePrincipalId" = "$($servicePrincipal.appId)"; "Password" = "$($servicePrincipal.password)"} -Force
Write-Host "WARNING: The Service Principal password is stored in clear at $jsonFile" -ForegroundColor Yellow
}
$customLocationRPOID = (az ad sp list --filter "displayname eq 'Custom Locations RP'" --query "[?appDisplayName=='Custom Locations RP'].id" -o tsv)
$jsonContent.Azure | Add-Member -MemberType NoteProperty -Name 'CustomLocationOID' -Value $customLocationRPOID -Force
#Adding Arc config as per AKSEdge schema
$arcdata = @{
Location = $aicfg.Location
ResourceGroupName = $aicfg.ResourceGroupName
SubscriptionId = $aicfg.SubscriptionId
TenantId = $aicfg.TenantId
ClientId = $aicfg.Auth.ServicePrincipalId
ClientSecret = $aicfg.Auth.Password
ClusterName = ""
}
$ecFile = $jsonContent.AksEdgeConfigFile
if ($ecFile) {
$parentpath = Split-Path -Path $jsonFile -Parent
if ($ecFile.Contains("\")) {
$ecFile = Resolve-Path -Path $ecFile
} else {
$ecFile = Join-Path -Path $parentpath -ChildPath $ecFile
}
Write-Host "Updating $ecFile with Arc information"
if (Test-Path -Path $ecFile) {
$edgeCfg = Get-Content $ecFile | ConvertFrom-Json
$edgeCfg | Add-Member -MemberType NoteProperty -Name 'Arc' -Value $arcdata -Force
$edgeCfg | ConvertTo-Json -Depth 6 | Format-Json | Set-Content -Path "$ecFile" -Force
}
} else {
$jsonContent | Add-Member -MemberType NoteProperty -Name 'Arc' -Value $arcdata -Force
}
$jsonContent | ConvertTo-Json -Depth 6 | Format-Json | Set-Content -Path "$jsonFile" -Force
az logout
exit 0