mirror of
https://dev.azure.com/effectory/Survey%20Software/_git/Cloud%20Engineering
synced 2026-02-27 18:52:18 +01:00
256 lines
12 KiB
PowerShell
256 lines
12 KiB
PowerShell
|
|
<#
|
|
.SYNOPSIS
|
|
Exports Azure DevOps service connections with detailed service principal information to a CSV file.
|
|
|
|
.DESCRIPTION
|
|
This script retrieves all service connections from an Azure DevOps project and analyzes their
|
|
associated service principals. It combines information from Azure DevOps REST API and Azure
|
|
PowerShell cmdlets to provide comprehensive details about:
|
|
- Service connection metadata (ID, name, status, authorization scheme)
|
|
- Associated service principal details (Application ID, Object ID, display name)
|
|
- Service principal credential expiration dates
|
|
|
|
This is particularly useful for security auditing and monitoring service principal
|
|
credential expiration to prevent service disruptions.
|
|
|
|
.PARAMETER Token
|
|
The Azure DevOps Personal Access Token (PAT) used for authentication. This token must have
|
|
'Service Connections (Read)' permissions to access service endpoint information.
|
|
|
|
.PARAMETER Organization
|
|
The Azure DevOps organization name. Defaults to "effectory" if not specified.
|
|
|
|
.PARAMETER Project
|
|
The Azure DevOps project name. Defaults to "Survey Software" if not specified.
|
|
|
|
.EXAMPLE
|
|
.\ServiceConnections.ps1 -Token "your-personal-access-token"
|
|
|
|
Analyzes service connections using the default organization and project settings.
|
|
|
|
.EXAMPLE
|
|
.\ServiceConnections.ps1 -Token "your-pat-token" -Organization "myorg" -Project "MyProject"
|
|
|
|
Analyzes service connections for a specific organization and project.
|
|
|
|
.OUTPUTS
|
|
Creates a timestamped CSV file in the current directory with the format: "yyyy-MM-dd HHmm serviceconnections.csv"
|
|
The CSV contains the following columns:
|
|
- Id: Service connection unique identifier
|
|
- Name: Service connection display name
|
|
- OperationStatus: Current operational status of the service connection
|
|
- AuthorizationScheme: Authentication method used (e.g., ServicePrincipal, ManagedServiceIdentity)
|
|
- ServicePrincipalApplicationId: Application (Client) ID of the associated service principal
|
|
- ServicePrincipalObjectId: Object ID of the service principal in Azure AD
|
|
- ServicePrincipalName: Display name of the service principal
|
|
- ServicePrincipalEndDateTime: Expiration date of the service principal credentials
|
|
|
|
.NOTES
|
|
Author: Cloud Engineering Team
|
|
Created: 2025
|
|
Requires: PowerShell 5.1 or later, Az PowerShell module
|
|
Dependencies: Az PowerShell module must be installed and authenticated to Azure
|
|
|
|
Prerequisites:
|
|
- Install Az PowerShell module: Install-Module -Name Az
|
|
- Connect to Azure: Connect-AzAccount
|
|
- Azure DevOps Personal Access Token with Service Connections (Read) permissions
|
|
- Appropriate Azure AD permissions to read service principal information
|
|
|
|
The script combines two data sources:
|
|
1. Azure DevOps REST API - for service connection metadata
|
|
2. Azure PowerShell cmdlets - for detailed service principal information
|
|
|
|
Security Considerations:
|
|
- Monitor credential expiration dates to prevent service disruptions
|
|
- Regular auditing of service connections helps maintain security posture
|
|
- Service principals with expired credentials will cause deployment failures
|
|
|
|
Error Handling:
|
|
- If service principal information cannot be retrieved, those fields will be empty
|
|
- The script continues processing even if individual service principals fail
|
|
- Network or authentication errors are handled gracefully
|
|
|
|
.LINK
|
|
https://docs.microsoft.com/en-us/rest/api/azure/devops/serviceendpoint/endpoints
|
|
https://docs.microsoft.com/en-us/powershell/module/az.resources/get-azadserviceprincipal
|
|
#>
|
|
|
|
param(
|
|
[Parameter(Mandatory = $true, HelpMessage = "Azure DevOps Personal Access Token with Service Connections (Read) permissions")]
|
|
[string]$Token,
|
|
|
|
[Parameter(Mandatory = $false, HelpMessage = "Azure DevOps organization name")]
|
|
[string]$Organization = "effectory",
|
|
|
|
[Parameter(Mandatory = $false, HelpMessage = "Azure DevOps project name")]
|
|
[string]$Project = "Survey Software"
|
|
)
|
|
|
|
# Define a class to structure service connection information with service principal details
|
|
class ServiceConnection {
|
|
[string] $Id = "" # Service connection unique identifier
|
|
[string] $Name = "" # Service connection display name
|
|
[string] $OperationStatus = "" # Current operational status
|
|
[string] $AuthorizationScheme = "" # Authentication method (e.g., ServicePrincipal)
|
|
[string] $ServicePrincipalApplicationId = "" # Application (Client) ID of service principal
|
|
[string] $ServicePrincipalObjectId = "" # Object ID of service principal in Azure AD
|
|
[string] $ServicePrincipalName = "" # Display name of service principal
|
|
[string] $ServicePrincipalEndDateTime = "" # Expiration date of service principal credentials
|
|
}
|
|
|
|
# Generate timestamped filename for the output CSV
|
|
[string] $date = Get-Date -Format "yyyy-MM-dd HHmm"
|
|
$fileName = ".\$date serviceconnections.csv"
|
|
|
|
# Display script execution banner
|
|
Write-Host "========================================================================================================================================================================"
|
|
Write-Host "Analyzing Azure DevOps service connections and associated service principals"
|
|
Write-Host "Organization: $Organization, Project: $Project"
|
|
Write-Host "Output file: $fileName"
|
|
Write-Host "Note: This script requires Azure PowerShell (Az module) to be installed and authenticated"
|
|
Write-Host "========================================================================================================================================================================"
|
|
|
|
# Prepare authentication for Azure DevOps REST API calls
|
|
# Personal Access Token must be base64 encoded with a colon prefix
|
|
$encodedToken = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes(":$($Token)"))
|
|
|
|
# URL-encode the project name for API calls
|
|
$projectEncoded = $Project -replace " ", "%20"
|
|
|
|
# Check if Azure PowerShell is available and user is authenticated
|
|
Write-Host "Verifying Azure PowerShell authentication..." -ForegroundColor Yellow
|
|
try {
|
|
$azContext = Get-AzContext
|
|
if (-not $azContext) {
|
|
Write-Host "ERROR: Not authenticated to Azure. Please run 'Connect-AzAccount' first." -ForegroundColor Red
|
|
exit 1
|
|
}
|
|
Write-Host "Azure authentication verified - Tenant: $($azContext.Tenant.Id)" -ForegroundColor Green
|
|
}
|
|
catch {
|
|
Write-Host "ERROR: Azure PowerShell module not found. Please install with 'Install-Module -Name Az'" -ForegroundColor Red
|
|
exit 1
|
|
}
|
|
|
|
# Retrieve service connections from Azure DevOps
|
|
Write-Host "Fetching service connections from Azure DevOps..." -ForegroundColor Yellow
|
|
$url="https://dev.azure.com/$Organization/$projectEncoded/_apis/serviceendpoint/endpoints?api-version=6.0-preview.4"
|
|
$head = @{ Authorization =" Basic $encodedToken" }
|
|
$response = Invoke-RestMethod -Uri $url -Method GET -Headers $head
|
|
Write-Host "Found $($response.count) service connections" -ForegroundColor Green
|
|
|
|
# Initialize array to store service connection analysis results
|
|
[ServiceConnection[]]$Result = @()
|
|
|
|
# Process each service connection to gather detailed information
|
|
$response.value | ForEach-Object {
|
|
Write-Host "Analyzing service connection: $($_.name)" -ForegroundColor Cyan
|
|
|
|
# Create new service connection object and populate basic information
|
|
[ServiceConnection] $serviceConnection = [ServiceConnection]::new()
|
|
$serviceConnection.Id = $_.id
|
|
$serviceConnection.Name = $_.name
|
|
$serviceConnection.OperationStatus = $_.operationStatus
|
|
$serviceConnection.AuthorizationScheme = $_.authorization.scheme
|
|
|
|
Write-Host " Authorization scheme: $($_.authorization.scheme)" -ForegroundColor Gray
|
|
Write-Host " Operation status: $($_.operationStatus)" -ForegroundColor Gray
|
|
|
|
# Extract service principal information if available
|
|
$principalid = $_.authorization.parameters.serviceprincipalid
|
|
if ($null -ne $principalid) {
|
|
Write-Host " Retrieving service principal details..." -ForegroundColor Gray
|
|
|
|
try {
|
|
# Get service principal information from Azure AD
|
|
$principal = Get-AzADServicePrincipal -ApplicationId $principalid -ErrorAction Stop
|
|
$credential = Get-AzADAppCredential -ApplicationId $principalid -ErrorAction Stop
|
|
|
|
$serviceConnection.ServicePrincipalApplicationId = $principalid
|
|
$serviceConnection.ServicePrincipalObjectId = $principal.Id
|
|
$serviceConnection.ServicePrincipalName = $principal.DisplayName
|
|
|
|
# Handle multiple credentials - get the latest expiration date
|
|
if ($credential) {
|
|
$latestExpiration = ($credential | Sort-Object EndDateTime -Descending | Select-Object -First 1).EndDateTime
|
|
$serviceConnection.ServicePrincipalEndDateTime = $latestExpiration
|
|
|
|
Write-Host " Service Principal: $($principal.DisplayName)" -ForegroundColor Gray
|
|
Write-Host " Application ID: $principalid" -ForegroundColor Gray
|
|
Write-Host " Credential expires: $latestExpiration" -ForegroundColor Gray
|
|
|
|
# Warn about expiring credentials (within 30 days)
|
|
if ($latestExpiration -and $latestExpiration -lt (Get-Date).AddDays(30)) {
|
|
Write-Host " WARNING: Credential expires within 30 days!" -ForegroundColor Red
|
|
}
|
|
}
|
|
else {
|
|
Write-Host " No credentials found for this service principal" -ForegroundColor Yellow
|
|
}
|
|
}
|
|
catch {
|
|
Write-Host " Error retrieving service principal details: $($_.Exception.Message)" -ForegroundColor Red
|
|
# Keep the Application ID even if other details fail
|
|
$serviceConnection.ServicePrincipalApplicationId = $principalid
|
|
}
|
|
}
|
|
else {
|
|
Write-Host " No service principal associated with this connection" -ForegroundColor Gray
|
|
}
|
|
|
|
# Add service connection to results array
|
|
$Result += $serviceConnection
|
|
}
|
|
|
|
# Export results to CSV file
|
|
$Result | Export-Csv -Path $fileName -NoTypeInformation
|
|
|
|
# Calculate and display summary statistics
|
|
$totalConnections = $Result.Count
|
|
$connectionsWithServicePrincipals = ($Result | Where-Object { $_.ServicePrincipalApplicationId -ne "" }).Count
|
|
$connectionsWithoutServicePrincipals = $totalConnections - $connectionsWithServicePrincipals
|
|
|
|
# Check for expiring credentials (within 30 days)
|
|
$expiringCredentials = $Result | Where-Object {
|
|
$_.ServicePrincipalEndDateTime -ne "" -and
|
|
[DateTime]::Parse($_.ServicePrincipalEndDateTime) -lt (Get-Date).AddDays(30)
|
|
}
|
|
|
|
# Check for expired credentials
|
|
$expiredCredentials = $Result | Where-Object {
|
|
$_.ServicePrincipalEndDateTime -ne "" -and
|
|
[DateTime]::Parse($_.ServicePrincipalEndDateTime) -lt (Get-Date)
|
|
}
|
|
|
|
# Display completion summary
|
|
Write-Host "========================================================================================================================================================================"
|
|
Write-Host "Service connection analysis completed successfully!" -ForegroundColor Green
|
|
Write-Host ""
|
|
Write-Host "SUMMARY STATISTICS:" -ForegroundColor Cyan
|
|
Write-Host "Total service connections: $totalConnections" -ForegroundColor Yellow
|
|
Write-Host "Connections with service principals: $connectionsWithServicePrincipals" -ForegroundColor Yellow
|
|
Write-Host "Connections without service principals: $connectionsWithoutServicePrincipals" -ForegroundColor Yellow
|
|
|
|
if ($expiredCredentials.Count -gt 0) {
|
|
Write-Host ""
|
|
Write-Host "EXPIRED CREDENTIALS (IMMEDIATE ATTENTION REQUIRED):" -ForegroundColor Red
|
|
$expiredCredentials | ForEach-Object {
|
|
Write-Host " - $($_.Name): $($_.ServicePrincipalName) (expired $($_.ServicePrincipalEndDateTime))" -ForegroundColor Red
|
|
}
|
|
}
|
|
|
|
if ($expiringCredentials.Count -gt 0) {
|
|
Write-Host ""
|
|
Write-Host "EXPIRING CREDENTIALS (WITHIN 30 DAYS):" -ForegroundColor Yellow
|
|
$expiringCredentials | ForEach-Object {
|
|
Write-Host " - $($_.Name): $($_.ServicePrincipalName) (expires $($_.ServicePrincipalEndDateTime))" -ForegroundColor Yellow
|
|
}
|
|
}
|
|
|
|
Write-Host ""
|
|
Write-Host "Output file: $fileName" -ForegroundColor Yellow
|
|
Write-Host "========================================================================================================================================================================"
|
|
|
|
|