Skip to content

Commit e3effca

Browse files
authored
Filter on appId (#48)
1 parent 40cfa12 commit e3effca

File tree

4 files changed

+280
-90
lines changed

4 files changed

+280
-90
lines changed

scripts/azure-devops/azure-pipelines.yml

+17-2
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,23 @@ jobs:
9292
inlineScript: |
9393
az account show -o json | ConvertFrom-Json | Set-Variable account
9494
95-
Write-Host "`nList Managed Identities in subscription '$($account.name)'"
96-
./list_service_connections.ps1 -TenantId $account.tenantId
95+
Write-Host "`nList Service Connection in subscription '$($account.name)'"
96+
./list_service_connections.ps1
97+
98+
failOnStandardError: true
99+
workingDirectory: '$(scriptDirectory)'
100+
101+
- task: AzureCLI@2
102+
displayName: 'list_service_connection_identities.ps1'
103+
inputs:
104+
azureSubscription: '$(azureConnection)'
105+
scriptType: pscore
106+
scriptLocation: inlineScript
107+
inlineScript: |
108+
az account show -o json | ConvertFrom-Json | Set-Variable account
109+
110+
Write-Host "`nList Service Connection Identities in tenant '$($account.name)'"
111+
./list_service_connection_identities.ps1 -TenantId $account.tenantId
97112
98113
failOnStandardError: true
99114
workingDirectory: '$(scriptDirectory)'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
#!/usr/bin/env pwsh
2+
<#
3+
.SYNOPSIS
4+
List Azure DevOps Service Connections
5+
.DESCRIPTION
6+
Use the Microsoft Graph API to find Azure DevOps Service Connections by organization & project, using Azure DevOps Service Connection naming convention
7+
#>
8+
#Requires -Version 7
9+
param (
10+
[parameter(Mandatory=$false,ParameterSetName="Organization",HelpMessage="Name of the Azure DevOps Organization")]
11+
[string]
12+
$Organization=(($env:AZDO_ORG_SERVICE_URL ?? $env:SYSTEM_COLLECTIONURI) -split '/' | Select-Object -Index 3),
13+
14+
[parameter(Mandatory=$false,ParameterSetName="Organization",HelpMessage="Name of the Azure DevOps Project")]
15+
[string]
16+
$Project,
17+
18+
[parameter(Mandatory=$false)]
19+
[switch]
20+
$HasCertificates=$false,
21+
22+
[parameter(Mandatory=$false)]
23+
[switch]
24+
$HasNoCertificates=$false,
25+
26+
[parameter(Mandatory=$false)]
27+
[switch]
28+
$HasFederation=$false,
29+
30+
[parameter(Mandatory=$false)]
31+
[switch]
32+
$HasNoFederation=$false,
33+
34+
[parameter(Mandatory=$false)]
35+
[switch]
36+
$HasSecrets=$false,
37+
38+
[parameter(Mandatory=$false)]
39+
[switch]
40+
$HasNoSecrets=$false,
41+
42+
[parameter(Mandatory=$false)]
43+
[guid[]]
44+
$AppId,
45+
46+
[parameter(Mandatory=$false,HelpMessage="Azure subscription id")]
47+
[ValidateNotNullOrEmpty()]
48+
[guid]
49+
$SubscriptionId,
50+
51+
[parameter(Mandatory=$false,HelpMessage="Azure Active Directory tenant id")]
52+
[guid]
53+
$TenantId=($env:ARM_TENANT_ID ?? $env:AZURE_TENANT_ID ?? [guid]::Empty),
54+
55+
[parameter(Mandatory=$false)]
56+
[ValidateSet('List', 'Table')]
57+
[string]
58+
$Format='Table'
59+
)
60+
61+
Write-Debug $MyInvocation.line
62+
. (Join-Path $PSScriptRoot .. functions.ps1)
63+
64+
# Login to Azure CLI
65+
Write-Verbose "Logging into Azure..."
66+
Login-Az -Tenant ([ref]$TenantId)
67+
68+
$message = "Identities of type 'Application' in Azure DevOps"
69+
if ($Organization) {
70+
$federationPrefix += "sc://${Organization}/"
71+
$namePrefix = "${Organization}-"
72+
$message += " organization '${Organization}'"
73+
} elseif (!$HasFederation) {
74+
Write-Warning "Organization not specified, listing all Service Connections with federation instead"
75+
$HasFederation = $true
76+
}
77+
if ($Project) {
78+
if (!$Organization) {
79+
Write-Warning "Project '${Project}' requires Organization to be specified"
80+
exit 1
81+
}
82+
$federationPrefix += "${Project}/"
83+
$namePrefix += "${Project}-"
84+
$message += " and project '${Project}'"
85+
}
86+
$federationPrefix ??= "sc://"
87+
88+
if ($HasFederation) {
89+
$message += " using federation"
90+
Write-Host "Searching for ${message}..."
91+
Find-ApplicationsByFederation -StartsWith $federationPrefix | Set-Variable msftGraphObjects
92+
} else {
93+
Write-Host "Searching for ${message}..."
94+
Find-ApplicationsByName -StartsWith $namePrefix | Set-Variable msftGraphObjects
95+
}
96+
97+
# Filters
98+
if ($AppId) {
99+
$AppId | Foreach-Object {$_.ToString().ToLower()} | Set-Variable AppId
100+
}
101+
Write-Host "${message}:"
102+
$msftGraphObjects | Where-Object {
103+
# We already check federation on organization/project, so we can ignore it here
104+
!$AppId -or ($_.appId.ToLower() -in $AppId)
105+
# } | Where-Object {
106+
# # We already check federation on organization/project, so we can ignore it here
107+
# !$HasFederation -or $_.name -match "${Organization}-[^-]+-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
108+
} | Where-Object {
109+
!$SubscriptionId -or $_.name -match $SubscriptionId
110+
} | Where-Object {
111+
$_.certCount -ge ($HasCertificates ? 1 : 0)
112+
} | Where-Object {
113+
!$HasNoCertificates -or $_.certCount -eq 0
114+
} | Where-Object {
115+
!$HasFederation -or $_.federatedSubjects -match "sc://[^/]+/[^/]+/[^/]+"
116+
} | Where-Object {
117+
!$HasNoFederation -or [string]::IsNullOrEmpty($_.federatedSubjects)
118+
} | Where-Object {
119+
$_.secretCount -ge ($HasSecrets ? 1 : 0)
120+
} | Where-Object {
121+
!$HasNoSecrets -or $_.secretCount -eq 0
122+
} | Set-Variable msftGraphFilteredObjects
123+
124+
125+
switch ($Format) {
126+
'List' {
127+
$msftGraphFilteredObjects | Format-List
128+
}
129+
'Table' {
130+
$msftGraphFilteredObjects | Format-Table -AutoSize
131+
}
132+
}
Original file line numberDiff line numberDiff line change
@@ -1,106 +1,126 @@
11
#!/usr/bin/env pwsh
22
<#
33
.SYNOPSIS
4-
Find Azure DevOps Service Connections
4+
List Azure DevOps Service Connections
55
.DESCRIPTION
6-
Use the Microsoft Graph API to find Azure DevOps Service Connections by organization & project, using Azure DevOps Service Connection naming convention
6+
Use the Azure CLI to find Azure DevOps Service Connections by organization & project
77
#>
8-
#Requires -Version 7
8+
#Requires -Version 7.2
9+
10+
[CmdletBinding(DefaultParameterSetName = 'name')]
911
param (
10-
[parameter(Mandatory=$false,ParameterSetName="Organization",HelpMessage="Name of the Azure DevOps Organization")]
11-
[string]
12-
$Organization=(($env:AZDO_ORG_SERVICE_URL ?? $env:SYSTEM_COLLECTIONURI) -split '/' | Select-Object -Index 3),
12+
[parameter(Mandatory=$false,ParameterSetName="app")]
13+
[guid[]]
14+
$AppId,
1315

14-
[parameter(Mandatory=$false,ParameterSetName="Organization",HelpMessage="Name of the Azure DevOps Project")]
16+
[parameter(Mandatory=$false,ParameterSetName="name",HelpMessage="Name of the Service Connection")]
1517
[string]
16-
$Project,
17-
18-
[parameter(Mandatory=$false)]
19-
[switch]
20-
$HasCertificates=$false,
21-
22-
[parameter(Mandatory=$false)]
23-
[switch]
24-
$HasNoCertificates=$false,
25-
26-
[parameter(Mandatory=$false)]
27-
[switch]
28-
$HasFederation=$false,
29-
30-
[parameter(Mandatory=$false)]
31-
[switch]
32-
$HasNoFederation=$false,
18+
$ServiceConnectionName,
3319

34-
[parameter(Mandatory=$false)]
35-
[switch]
36-
$HasSecrets=$false,
37-
38-
[parameter(Mandatory=$false)]
39-
[switch]
40-
$HasNoSecrets=$false,
20+
[parameter(Mandatory=$false,HelpMessage="Name of the Azure DevOps Project")]
21+
[string]
22+
[ValidateNotNullOrEmpty()]
23+
$Project=$env:SYSTEM_TEAMPROJECT,
4124

42-
[parameter(Mandatory=$false,HelpMessage="Azure subscription id")]
25+
[parameter(Mandatory=$false,HelpMessage="Url of the Azure DevOps Organization")]
26+
[uri]
4327
[ValidateNotNullOrEmpty()]
44-
[guid]
45-
$SubscriptionId,
28+
$OrganizationUrl=($env:AZDO_ORG_SERVICE_URL ?? $env:SYSTEM_COLLECTIONURI),
4629

47-
[parameter(Mandatory=$false,HelpMessage="Azure Active Directory tenant id")]
48-
[guid]
49-
$TenantId=($env:ARM_TENANT_ID ?? $env:AZURE_TENANT_ID ?? [guid]::Empty)
30+
[parameter(Mandatory=$false)]
31+
[ValidateSet('List', 'Table')]
32+
[string]
33+
$Format='List'
5034
)
51-
52-
Write-Debug $MyInvocation.line
35+
Write-Verbose $MyInvocation.line
5336
. (Join-Path $PSScriptRoot .. functions.ps1)
37+
$apiVersion = "7.1"
38+
if ($AppId) {
39+
$AppId | Foreach-Object {$_.ToString().ToLower()} | Set-Variable AppId
40+
}
5441

55-
# Login to Azure CLI
56-
Write-Verbose "Logging into Azure..."
57-
Login-Az -Tenant ([ref]$TenantId)
58-
59-
$message = "Identities of type 'Application' in Azure DevOps"
60-
if ($Organization) {
61-
$federationPrefix += "sc://${Organization}/"
62-
$namePrefix = "${Organization}-"
63-
$message += " organization '${Organization}'"
64-
} elseif (!$HasFederation) {
65-
Write-Warning "Organization not specified, listing all Service Connections with federation instead"
66-
$HasFederation = $true
42+
#-----------------------------------------------------------
43+
# Log in to Azure
44+
if (-not (Get-Command az -ErrorAction SilentlyContinue)) {
45+
Write-Error "Azure CLI is not installed. You can get it here: http://aka.ms/azure-cli"
46+
exit 1
6747
}
68-
if ($Project) {
69-
if (!$Organization) {
70-
Write-Warning "Project '${Project}' requires Organization to be specified"
71-
exit 1
72-
}
73-
$federationPrefix += "${Project}/"
74-
$namePrefix += "${Project}-"
75-
$message += " and project '${Project}'"
48+
az account show -o json 2>$null | ConvertFrom-Json | Set-Variable account
49+
if (!$account) {
50+
az login --allow-no-subscriptions -o json | ConvertFrom-Json | Set-Variable account
51+
}
52+
# Log in to Azure & Azure DevOps
53+
$OrganizationUrl = $OrganizationUrl.ToString().Trim('/')
54+
az account get-access-token --resource 499b84ac-1321-427f-aa17-267ca6975798 `
55+
--query "accessToken" `
56+
--output tsv `
57+
| Set-Variable accessToken
58+
if (!$accessToken) {
59+
Write-Error "$(account.user.name) failed to get access token for Azure DevOps"
60+
exit 1
61+
}
62+
if (!(az extension list --query "[?name=='azure-devops'].version" -o tsv)) {
63+
Write-Host "Adding Azure CLI extension 'azure-devops'..."
64+
az extension add -n azure-devops -y -o none
65+
}
66+
$accessToken | az devops login --organization $OrganizationUrl
67+
if ($lastexitcode -ne 0) {
68+
Write-Error "$($account.user.name) failed to log in to Azure DevOps organization '${OrganizationUrl}'"
69+
exit $lastexitcode
7670
}
77-
$federationPrefix ??= "sc://"
7871

79-
if ($HasFederation) {
80-
$message += " using federation"
81-
Write-Host "Searching for ${message}..."
82-
Find-ApplicationsByFederation -StartsWith $federationPrefix | Set-Variable msftGraphObjects
83-
} else {
84-
Write-Host "Searching for ${message}..."
85-
Find-ApplicationsByName -StartsWith $namePrefix | Set-Variable msftGraphObjects
72+
#-----------------------------------------------------------
73+
# Check parameters
74+
az devops project show --project "${Project}" --organization $OrganizationUrl --query id -o tsv | Set-Variable projectId
75+
if (!$projectId) {
76+
Write-Error "Project '${Project}' not found in organization '${OrganizationUrl}"
77+
exit 1
8678
}
8779

88-
Write-Host "${message}:"
89-
$msftGraphObjects | Where-Object {
90-
# We already check federation on organization/project, so we can ignore it here
91-
!$HasFederation -or $_.name -match "${Organization}-[^-]+-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
92-
} | Where-Object {
93-
!$SubscriptionId -or $_.name -match $SubscriptionId
94-
} | Where-Object {
95-
$_.certCount -ge ($HasCertificates ? 1 : 0)
96-
} | Where-Object {
97-
!$HasNoCertificates -or $_.certCount -eq 0
98-
} | Where-Object {
99-
!$HasFederation -or $_.federatedSubjects -match "sc://[^/]+/[^/]+/[^/]+"
100-
} | Where-Object {
101-
!$HasNoFederation -or [string]::IsNullOrEmpty($_.federatedSubjects)
102-
} | Where-Object {
103-
$_.secretCount -ge ($HasSecrets ? 1 : 0)
80+
#-----------------------------------------------------------
81+
# Retrieve the service connection
82+
83+
$getApiUrl = "${OrganizationUrl}/${Project}/_apis/serviceendpoint/endpoints?type=azurerm&includeFailed=true&includeDetails=true&api-version=${apiVersion}"
84+
az rest --resource 499b84ac-1321-427f-aa17-267ca6975798 -u "${getApiUrl} " -m GET --query "sort_by(value[?authorization.scheme=='ServicePrincipal' && data.creationMode=='Automatic' && !(isShared && serviceEndpointProjectReferences[0].projectReference.name!='${Project}')],&name)" -o json `
85+
| Tee-Object -Variable rawResponse `
86+
| ConvertFrom-Json `
87+
| Tee-Object -Variable serviceEndpoints `
88+
| Format-List | Out-String | Write-Debug
89+
if (!$serviceEndpoints -or ($serviceEndpoints.count-eq 0)) {
90+
Write-Warning "No convertible service connections found"
91+
exit 1
92+
}
93+
94+
$serviceEndpoints | ForEach-Object {
95+
"https://portal.azure.com/{0}/#blade/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/Overview/appId/{1}" -f $_.authorization.parameters.tenantId, $_.authorization.parameters.servicePrincipalId | Set-Variable applicationPortalLink
96+
$_ | Add-Member -NotePropertyName applicationPortalLink -NotePropertyValue $applicationPortalLink
97+
"{0}/{1}/_settings/adminservices?resourceId={2}" -f $OrganizationUrl, $_.serviceEndpointProjectReferences[0].projectReference.id, $_.id | Set-Variable serviceConnectionPortalLink
98+
$_ | Add-Member -NotePropertyName serviceConnectionPortalLink -NotePropertyValue $serviceConnectionPortalLink
99+
$_ | Add-Member -NotePropertyName authorizationScheme -NotePropertyValue $_.authorization.scheme
100+
$_ | Add-Member -NotePropertyName appId -NotePropertyValue $_.authorization.parameters.servicePrincipalId.ToLower()
101+
102+
$_
104103
} | Where-Object {
105-
!$HasNoSecrets -or $_.secretCount -eq 0
106-
} | Format-Table -AutoSize
104+
# We already check federation on organization/project, so we can ignore it here
105+
!$AppId -or ($_.appId -in $AppId)
106+
} | Set-Variable filteredServiceEndpoints
107+
108+
switch ($Format) {
109+
'List' {
110+
$filteredServiceEndpoints | Format-List
111+
}
112+
'Table' {
113+
$filteredServiceEndpoints | Format-Table -AutoSize -Property name, authorizationScheme, appId
114+
}
115+
}
116+
$filteredServiceEndpoints | ForEach-Object {
117+
$_.appId
118+
} | Set-Variable matchedAppIds
119+
Write-Host "Matched AppIds: $($matchedAppIds -join ', ')"
120+
$AppId | Where-Object {
121+
$_ -notin $matchedAppIds
122+
}
123+
| Set-Variable unmatchedAppIds
124+
if ($unmatchedAppIds) {
125+
Write-Warning "Unmatched AppIds: $($unmatchedAppIds -join ', ')"
126+
}

0 commit comments

Comments
 (0)