-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathCreate-ServiceConnection.ps1
More file actions
268 lines (230 loc) · 12.4 KB
/
Create-ServiceConnection.ps1
File metadata and controls
268 lines (230 loc) · 12.4 KB
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
<#
.SYNOPSIS
Creates required resources for deploying Application Landing Zone components from Azure Devops.
Resources created:
- Resource Group
- User Assigned Identity
- Role Assignments at Subscription level
- Contributor
- User Access Administrator
- Write/Delete for Owner, Role Based Access Control Administrator and User Access Administrator roles excluded
- Federated Identity Credentials
- Service Connection
.DESCRIPTION
Requirements
- User Access Administrator or Owner role to the target Subscription
- Personal Access Token (PAT) to the target ADO Organization with scope:
- Service Connections
- Read, query, & manage
.PARAMETER AzureDevOpsOrganisationName
Azure Devops Organization name where the Service Connection will be created.
.PARAMETER AzureDevOpsProjectName
Azure Devops Project name where the Service Connection will be created.
.PARAMETER SubscriptionId
The Landing Zone subscription Id where resources are being deployed and which the Service Connection will have access to.
.PARAMETER LandingzoneName
The Landing Zone name. Value must be greater than 5 characters and less than 100 characters.
.PARAMETER Environment
Environment type. Valid values are 'dev', 'test' and 'prod'.
.EXAMPLE
.\Create-ServiceConnection.ps1 -AzureDevOpsOrganisationName contoso -AzureDevOpsProjectName landingzones -SubscriptionId 11111111-2222-3333-4444-555555555555 -LandingzoneName application1 -Environment dev
Creates the Service Connection to Azure Devops Organization 'contoso' to Project 'landingzones'. Azure Resources will be created to subscription '11111111-2222-3333-4444-555555555555'.
#>
param (
[Parameter(Mandatory=$true)]
[string] $AzureDevOpsOrganisationName,
[Parameter(Mandatory=$true)]
[string] $AzureDevOpsProjectName,
[Parameter(Mandatory=$true)]
[string] $SubscriptionId,
[Parameter(Mandatory=$true)]
[ValidateLength(5,100)]
[string] $LandingzoneName,
[Parameter(Mandatory=$true)]
[ValidateSet("dev","test", "prod")]
[string] $Environment,
[Parameter(Mandatory=$true)]
[string] $PAT
)
# Variables
$ResourceGroupName = 'rg-identity'
$Location = 'westeurope'
$AzureDevOpsAuthenicationHeader = @{Authorization = 'Basic ' + [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(":$($PAT)")) }
$ManagedIdentityName = 'id-'+$Environment+'-lz-'+$LandingzoneName
$ServiceConnectionName = 'sc-id-'+$Environment+'-lz-'+$LandingzoneName
$FederatedIdentityName = 'federatedIdentityCredentialsAzureDevOps'
$Scope = '/subscriptions/'+$SubscriptionId
# Condition for User Access Administrator role assignment
# Prevents creation/deletion for Owner, Role Based Access Control Administrator and User Access Administrator roles
$UserAccessAdminCondition = "
(
(
!(ActionMatches{'Microsoft.Authorization/roleAssignments/write'})
)
OR
(
@Request[Microsoft.Authorization/roleAssignments:RoleDefinitionId] ForAnyOfAllValues:GuidNotEquals {8e3af657-a8ff-443c-a75c-2fe8c4bcb635, 18d7d88d-d35e-4fb5-a5c3-7773c20a72d9, f58310d9-a9f6-439a-9e8d-f62e7b41a168}
)
)
AND
(
(
!(ActionMatches{'Microsoft.Authorization/roleAssignments/delete'})
)
OR
(
@Resource[Microsoft.Authorization/roleAssignments:RoleDefinitionId] ForAnyOfAllValues:GuidNotEquals {8e3af657-a8ff-443c-a75c-2fe8c4bcb635, 18d7d88d-d35e-4fb5-a5c3-7773c20a72d9, f58310d9-a9f6-439a-9e8d-f62e7b41a168}
)
)
"
$ConditionVersion = "2.0"
# Flush variables
$issuer = ''
$subjectIdentifier = ''
# Output a summary of user input
Write-Host "Provided values" -ForegroundColor Blue
Write-Host "Azure DevOps Organisation Name: " -NoNewLine -ForegroundColor Yellow
Write-Host $AzureDevOpsOrganisationName
Write-Host "Azure DevOps Project Name: " -NoNewLine -ForegroundColor Yellow
Write-Host $AzureDevOpsProjectName
Write-Host "Subscription Id: " -NoNewLine -ForegroundColor Yellow
Write-Host $SubscriptionId
Write-Host "Landing Zone Name: " -NoNewLine -ForegroundColor Yellow
Write-Host $LandingzoneName
Write-Host "Environment: " -NoNewLine -ForegroundColor Yellow
Write-Host $Environment
Write-Host ""
Write-Host "With these values the following resources are being created:" -ForegroundColor Blue
Write-Host "User assigned identity: " -NoNewLine -ForegroundColor Yellow
Write-Host $ManagedIdentityName
Write-Host "Federated Identity Credentials: " -NoNewLine -ForegroundColor Yellow
Write-Host "Issuer=https://vstoken.dev.azure.com/<Organization Id>, Subject Identifier=sc://$AzureDevOpsOrganisationName/$AzureDevOpsProjectName/$ServiceConnectionName"
Write-Host "Role assignment: " -NoNewLine -ForegroundColor Yellow
Write-Host "Contributor and User Access Administrator roles for $ManagedIdentityName to $Scope"
Write-Host "Service Connection: " -NoNewLine -ForegroundColor Yellow
Write-Host $ServiceConnectionName
Write-Host ""
Write-Warning "Check values above and Confirm (Y) or Exit (S)" -WarningAction Inquire
Write-Host ""
Write-Host ""
try {
# Check if we can switch to the subscription selected
if(!(Set-AzContext -Subscription $SubscriptionId -ErrorAction SilentlyContinue)) {
Write-Host "ERROR: Unable to switch to subscription $subscriptionId. Please provide a valid subscriptionId." -ForegroundColor Red
exit
}
else {
$currentSubscription = Get-AzContext
Write-Host "Current subscription is $($currentSubscription.Subscription.Name) $($currentSubscription.Subscription.Id)"
$SubscriptionName = (Get-AzSubscription -SubscriptionId $SubscriptionId).Name
}
# Retrieving Azure DevOps Organisation ID
$restApiAdoOrgInfo = "https://dev.azure.com/$AzureDevOpsOrganisationName/_apis/connectiondata?api-version=5.0-preview.1"
$azureDevOpsOrganisationId = Invoke-RestMethod -Uri $restApiAdoOrgInfo -Headers $AzureDevOpsAuthenicationHeader -Method Get | Select-Object -ExpandProperty instanceId
# Retrieve Azure DevOps Project ID
$restApiAdoProjectInfo = "https://dev.azure.com/$AzureDevOpsOrganisationName/_apis/projects/$($AzureDevOpsProjectName)?api-version=7.1-preview.4"
$azureDevOpsProjectId = Invoke-RestMethod -Uri $restApiAdoProjectInfo -Headers $AzureDevOpsAuthenicationHeader -Method Get | Select-Object -ExpandProperty id
# OIDC information needed for User Assigned Managed Identity
$issuer = "https://vstoken.dev.azure.com/$azureDevOpsOrganisationId"
$subjectIdentifier = "sc://$AzureDevOpsOrganisationName/$AzureDevOpsProjectName/$ServiceConnectionName"
# Create Resource Group if it doesn't exist
if(!(Get-AzResourceGroup -Name $ResourceGroupName -Location $Location -ErrorAction SilentlyContinue)) {
Write-Host "Creating Resource Group $ResourceGroupName to $Location " -ForegroundColor Green
New-AzResourceGroup -Name $ResourceGroupName -Location $Location | Out-Null
}
else {
Write-Host "Resource Group $ResourceGroupName at $Location already exists." -ForegroundColor Yellow
}
# Create User Assigned Identity if it doesn't exist
if(!(Get-AzUserAssignedIdentity -ResourceGroupName $ResourceGroupName -Name $ManagedIdentityName -ErrorAction SilentlyContinue)) {
Write-Host "Creating User Assigned Identity $ManagedIdentityName to $ResourceGroupName at $Location " -ForegroundColor Green
New-AzUserAssignedIdentity -Location $Location -ResourceGroupName $ResourceGroupName -Name $ManagedIdentityName | Out-Null
Write-Host "Waiting 20 seconds for replication..."
Start-Sleep -Seconds 20
}
else {
Write-Host "User Assigned Identity $ManagedIdentityName in $ResourceGroupName at $Location already exists" -ForegroundColor Yellow
}
# Gather required parameters from the User Assigned Identity
$clientId = (Get-AzUserAssignedIdentity -Name $ManagedIdentityName -ResourceGroupName $ResourceGroupName).ClientId
$tenantId = (Get-AzUserAssignedIdentity -Name $ManagedIdentityName -ResourceGroupName $ResourceGroupName).TenantId
$principalId = (Get-AzUserAssignedIdentity -Name $ManagedIdentityName -ResourceGroupName $ResourceGroupName).PrincipalId
# Create Role Assignments if they don't exist
# Contributor
if(Get-AzRoleAssignment -ObjectId $principalId -Scope $Scope -RoleDefinitionName 'Contributor') {
Write-Host "Contributor role assignment for $principalId at scope $Scope already exists" -ForegroundColor Yellow
}
else {
New-AzRoleAssignment -ObjectId $principalId -Scope $Scope -RoleDefinitionName 'Contributor' | Out-Null
Write-Host "New Contributor role assignment created to scope $scope for $principalId" -ForegroundColor Green
}
# User Access administrator with conditions
if(Get-AzRoleAssignment -ObjectId $principalId -Scope $Scope -RoleDefinitionName 'User Access Administrator') {
Write-Host "User Access Administrator role assignment for $principalId at scope $Scope already exists" -ForegroundColor Yellow
}
else {
New-AzRoleAssignment -ObjectId $principalId -Scope $Scope -RoleDefinitionName 'User Access Administrator' -Condition $UserAccessAdminCondition -ConditionVersion $ConditionVersion| Out-Null
Write-Host "New User Access Administrator role assignment created to scope $scope for $principalId" -ForegroundColor Green
}
# Create Federated Identity Credentials if it doesn't exist
if(!(Get-AzFederatedIdentityCredentials -ResourceGroupName $ResourceGroupName -IdentityName $ManagedIdentityName -Name $FederatedIdentityName -ErrorAction SilentlyContinue)) {
Write-Host "Creating Federated Identity Credential for $ManagedIdentityName with issuer $issuer and subject $subjectIdentifier" -ForegroundColor Green
New-AzFederatedIdentityCredentials -ResourceGroupName $ResourceGroupName -IdentityName $ManagedIdentityName -Name $FederatedIdentityName -Issuer $issuer -Subject $subjectIdentifier -Audience 'api://AzureADTokenExchange' | Out-Null
}
else {
Write-Host "Federated Identity Credential for $ManagedIdentityName with issuer $issuer and subject $subjectIdentifier already exists" -ForegroundColor Yellow
}
# Populate ADO Endpoint URL and check if the Service Connection already exists.
$getServiceConnectionEndpointUrl = "https://dev.azure.com/$AzureDevOpsOrganisationName/$AzureDevOpsProjectName/_apis/serviceendpoint/endpoints?api-version=7.1-preview.4"
$existing = Invoke-RestMethod -Uri $getServiceConnectionEndpointUrl -Headers $AzureDevOpsAuthenicationHeader -Method Get | Select-Object -ExpandProperty value | Where-Object { $_.name -eq $ServiceConnectionName } | Select-Object -ExpandProperty id
if($existing) {
Write-Host "Service Connection ($ServiceConnectionName) already exists." -ForegroundColor Yellow
}
# Create a new one if doesn't.
else {
$body = @"
{
"authorization": {
"parameters": {
"serviceprincipalid": "$clientId",
"tenantid": "$tenantId"
},
"scheme": "WorkloadIdentityFederation"
},
"createdBy": {},
"data": {
"environment": "AzureCloud",
"scopeLevel": "Subscription",
"creationMode": "Manual",
"subscriptionId": "$SubscriptionId",
"subscriptionName": "$SubscriptionName"
},
"isShared": false,
"isOutdated": false,
"isReady": false,
"name": "$ServiceConnectionName",
"owner": "library",
"type": "AzureRM",
"url": "https://management.azure.com/",
"description": "",
"serviceEndpointProjectReferences": [
{
"description": "Service connection to Landing Zone $SubscriptionName",
"name": "$ServiceConnectionName",
"projectReference": {
"id": "$azureDevOpsProjectId",
"name": "$AzureDevOpsProjectName"
}
}
]
}
"@
# Registering OIDC service connection
$restApiEndpointUrl = "https://dev.azure.com/$AzureDevOpsOrganisationName/_apis/serviceendpoint/endpoints?api-version=7.1-preview.4"
$serviceConnection = Invoke-RestMethod -Uri $restApiEndpointUrl -Headers $AzureDevOpsAuthenicationHeader -Method Post -Body $body -ContentType 'application/json'
Write-Host "Service Connection $serviceConnectionName created to project $AzureDevopsProjectName" -ForegroundColor Green
}
}
catch {
Write-host -f red "Encountered Error:"$_.Exception.Message
}