mirror of
https://dev.azure.com/effectory/Survey%20Software/_git/Cloud%20Engineering
synced 2026-02-27 18:52:18 +01:00
357 lines
17 KiB
PowerShell
357 lines
17 KiB
PowerShell
<#
|
||
.SYNOPSIS
|
||
Inventories and monitors Azure App Service certificates across all enabled subscriptions.
|
||
|
||
.DESCRIPTION
|
||
This script performs a comprehensive audit of all Azure App Service certificates across all
|
||
enabled subscriptions in your Azure tenant. It extracts certificate details including expiration
|
||
dates, thumbprints, subject names, and calculates the remaining days until expiration.
|
||
|
||
The script is designed for:
|
||
- Certificate lifecycle management and monitoring
|
||
- Proactive identification of expiring certificates
|
||
- Compliance auditing and reporting
|
||
- Security assessments of certificate inventory
|
||
- Planning certificate renewal activities
|
||
|
||
The script processes all enabled subscriptions automatically and exports results to a timestamped
|
||
CSV file, making it suitable for automated monitoring and reporting workflows.
|
||
|
||
Key features:
|
||
- Multi-subscription certificate discovery
|
||
- Expiration date calculation with days remaining
|
||
- Error handling for inaccessible or invalid certificates
|
||
- Detailed logging and progress reporting
|
||
- CSV export for further analysis and alerting
|
||
|
||
.PARAMETER None
|
||
This script does not accept parameters and processes all enabled subscriptions automatically.
|
||
|
||
.EXAMPLE
|
||
.\Certificates.ps1
|
||
|
||
Runs the certificate inventory across all enabled subscriptions and exports results to a
|
||
timestamped CSV file in the current directory.
|
||
|
||
.EXAMPLE
|
||
# Schedule for automated monitoring
|
||
$scriptPath = "C:\Scripts\Certificates.ps1"
|
||
& $scriptPath
|
||
|
||
Executes the script from a scheduled task or automation workflow for regular certificate monitoring.
|
||
|
||
.EXAMPLE
|
||
# Run and immediately view results
|
||
.\Certificates.ps1
|
||
Get-Content ".\$(Get-Date -Format 'yyyy-MM-dd HHmm') azure_appservice_certificates.csv"
|
||
|
||
Runs the script and displays the generated CSV content for immediate review.
|
||
|
||
.NOTES
|
||
Author: Cloud Engineering Team
|
||
Version: 1.0
|
||
|
||
Prerequisites:
|
||
- Azure PowerShell module (Az) must be installed
|
||
- User must be authenticated to Azure (Connect-AzAccount)
|
||
- User must have at least 'Reader' permissions across target subscriptions
|
||
- Access to Microsoft.Web/certificates resources
|
||
|
||
Required Permissions:
|
||
- Reader access to subscriptions containing App Service certificates
|
||
- Web App Certificate Reader or App Service Certificate Reader permissions
|
||
- Resource Group Reader permissions for certificate resource groups
|
||
|
||
Output File:
|
||
- Format: "YYYY-MM-DD HHMM azure_appservice_certificates.csv"
|
||
- Location: Current directory
|
||
- Content: Certificate inventory with expiration analysis
|
||
|
||
Certificate Status Analysis:
|
||
- TotalDays > 30: Certificate is healthy
|
||
- TotalDays 7-30: Certificate expires soon (warning)
|
||
- TotalDays < 7: Certificate expires very soon (critical)
|
||
- TotalDays < 0: Certificate has already expired (urgent action required)
|
||
|
||
Performance Considerations:
|
||
- Processing time depends on the number of subscriptions and certificates
|
||
- Large tenants with many certificates may require extended execution time
|
||
- Network latency affects certificate detail retrieval
|
||
|
||
Security and Compliance:
|
||
- Certificate thumbprints and subject names are included in output
|
||
- Ensure proper access controls on generated CSV files
|
||
- Consider encryption for sensitive certificate inventory data
|
||
- Regular execution recommended for proactive certificate management
|
||
|
||
Common Use Cases:
|
||
- Monthly certificate expiration reports
|
||
- Pre-renewal planning and notifications
|
||
- Compliance audits requiring certificate inventory
|
||
- Security assessments of certificate lifecycle management
|
||
|
||
.LINK
|
||
https://docs.microsoft.com/en-us/azure/app-service/configure-ssl-certificate
|
||
https://docs.microsoft.com/en-us/powershell/module/az.websites/
|
||
#>
|
||
|
||
# Ensure user is authenticated to Azure
|
||
# Uncomment the following line if authentication is needed:
|
||
# Connect-AzAccount
|
||
|
||
# Display script header and configuration
|
||
Write-Host "======================================================================================================================================================================"
|
||
Write-Host "Azure App Service Certificate Inventory and Monitoring"
|
||
Write-Host "======================================================================================================================================================================"
|
||
Write-Host "Starting certificate discovery across all enabled subscriptions..."
|
||
Write-Host "Script execution started: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')"
|
||
Write-Host ""
|
||
|
||
# Generate timestamped filename for export
|
||
[string] $date = Get-Date -Format "yyyy-MM-dd HHmm"
|
||
$fileName = ".\$date azure_appservice_certificates.csv"
|
||
Write-Host "Export file: $fileName"
|
||
Write-Host ""
|
||
|
||
# Get all enabled subscriptions for processing
|
||
Write-Host "Retrieving enabled subscriptions..."
|
||
try {
|
||
$subscriptions = Get-AzSubscription | Where-Object State -eq "Enabled"
|
||
Write-Host "✓ Found $($subscriptions.Count) enabled subscription(s) to process:"
|
||
foreach ($sub in $subscriptions) {
|
||
Write-Host " - $($sub.Name) ($($sub.Id))"
|
||
}
|
||
} catch {
|
||
Write-Error "Failed to retrieve Azure subscriptions. Please ensure you are authenticated (Connect-AzAccount)"
|
||
throw $_
|
||
}
|
||
Write-Host ""
|
||
|
||
# Define certificate information class for structured data collection
|
||
class CertificateCheck {
|
||
# Azure subscription identifier containing the certificate
|
||
[string] $SubscriptionId = ""
|
||
|
||
# Full Azure resource ID of the certificate
|
||
[string] $CertificateId = ""
|
||
|
||
# Resource group name where the certificate is deployed
|
||
[string] $ResourceGroupName = ""
|
||
|
||
# Certificate subject name (Common Name and additional fields)
|
||
[string] $SubjectName = ""
|
||
|
||
# Certificate thumbprint (SHA-1 hash identifier)
|
||
[string] $ThumbPrint = ""
|
||
|
||
# Certificate expiration date and time
|
||
[DateTime] $ExpirationDate
|
||
|
||
# Number of days remaining until expiration (negative if expired)
|
||
[double] $TotalDays
|
||
|
||
# Certificate health status (Expired, Critical, Warning, Healthy, Error)
|
||
[string] $Health = ""
|
||
|
||
# Error messages or status comments for problematic certificates
|
||
[string] $Comment = ""
|
||
}
|
||
|
||
# Initialize result collection and processing variables
|
||
[CertificateCheck[]]$Result = @()
|
||
$StartDate = (Get-Date)
|
||
$totalCertificates = 0
|
||
$processedSubscriptions = 0
|
||
$certificatesWithIssues = 0
|
||
|
||
Write-Host "======================================================================================================================================================================"
|
||
Write-Host "Processing Certificates by Subscription"
|
||
Write-Host "======================================================================================================================================================================"
|
||
|
||
# Process each enabled subscription
|
||
foreach ($subscription in $subscriptions) {
|
||
Write-Host ""
|
||
Write-Host "Processing subscription: $($subscription.Name) ($($subscription.Id))"
|
||
|
||
try {
|
||
# Set Azure context to current subscription
|
||
Set-AzContext -SubscriptionId $subscription.Id -ErrorAction Stop | Out-Null
|
||
Write-Host "✓ Successfully connected to subscription"
|
||
|
||
# Retrieve all App Service certificates in the subscription
|
||
Write-Host "Discovering App Service certificates..."
|
||
$certs = Get-AzResource -ResourceType Microsoft.Web/certificates -ApiVersion "2018-02-01" -ExpandProperties | Select-Object * -ExpandProperty Properties
|
||
|
||
if ($certs) {
|
||
Write-Host "✓ Found $($certs.Count) certificate(s) in subscription"
|
||
$subscriptionCertCount = 0
|
||
|
||
# Process each certificate found
|
||
foreach ($cert in $certs) {
|
||
$id = $cert.Id
|
||
Write-Host " Processing certificate: $($cert.Name)"
|
||
|
||
# Create new certificate check instance
|
||
[CertificateCheck] $certificateCheck = [CertificateCheck]::new()
|
||
|
||
# Populate basic certificate information
|
||
$certificateCheck.SubscriptionId = $subscription.Id
|
||
$certificateCheck.CertificateId = $id
|
||
$certificateCheck.ThumbPrint = $cert.Properties.thumbprint
|
||
$certificateCheck.ResourceGroupName = $cert.ResourceGroupName
|
||
|
||
try {
|
||
$thumbprint = $certificateCheck.ThumbPrint
|
||
|
||
# Retrieve detailed certificate information
|
||
$certificate = Get-AzWebAppCertificate -ResourceGroupName $certificateCheck.ResourceGroupName -Thumbprint $thumbprint -ErrorAction Stop
|
||
|
||
if ($null -eq $certificate) {
|
||
$certificateCheck.Health = "Error"
|
||
$certificateCheck.Comment = "Could not find certificate details"
|
||
$certificatesWithIssues++
|
||
Write-Host " ⚠ Warning: Certificate details not accessible"
|
||
} else {
|
||
try {
|
||
# Extract certificate subject name and expiration details
|
||
$subjectname = $certificate.SubjectName
|
||
$certificateCheck.SubjectName = $subjectname
|
||
|
||
Write-Host " ✓ Subject: $subjectname"
|
||
|
||
# Calculate expiration and days remaining
|
||
$EndDate = [datetime]$certificate.ExpirationDate
|
||
$certificateCheck.ExpirationDate = $EndDate
|
||
$span = New-TimeSpan -Start $StartDate -End $EndDate
|
||
$certificateCheck.TotalDays = [Math]::Round($span.TotalDays, 1)
|
||
|
||
# Determine and assign health status based on expiration
|
||
if ($certificateCheck.TotalDays -lt 0) {
|
||
$certificateCheck.Health = "Expired"
|
||
Write-Host " 🔴 EXPIRED: $([Math]::Abs($certificateCheck.TotalDays)) days ago" -ForegroundColor Red
|
||
$certificatesWithIssues++
|
||
} elseif ($certificateCheck.TotalDays -lt 7) {
|
||
$certificateCheck.Health = "Critical"
|
||
Write-Host " 🟠 CRITICAL: Expires in $($certificateCheck.TotalDays) days" -ForegroundColor Yellow
|
||
$certificatesWithIssues++
|
||
} elseif ($certificateCheck.TotalDays -lt 30) {
|
||
$certificateCheck.Health = "Warning"
|
||
Write-Host " 🟡 WARNING: Expires in $($certificateCheck.TotalDays) days" -ForegroundColor Yellow
|
||
} else {
|
||
$certificateCheck.Health = "Healthy"
|
||
Write-Host " ✓ Healthy: Expires in $($certificateCheck.TotalDays) days"
|
||
}
|
||
} catch {
|
||
$certificateCheck.Health = "Error"
|
||
$certificateCheck.Comment = "Could not determine expiration date"
|
||
$certificatesWithIssues++
|
||
Write-Host " ⚠ Warning: Could not determine expiration date"
|
||
}
|
||
}
|
||
} catch {
|
||
$certificateCheck.Health = "Error"
|
||
$certificateCheck.Comment = "Could not load certificate details"
|
||
$certificatesWithIssues++
|
||
Write-Host " ❌ Error: Could not load certificate details"
|
||
}
|
||
|
||
# Add certificate to results collection
|
||
$Result += $certificateCheck
|
||
$totalCertificates++
|
||
$subscriptionCertCount++
|
||
}
|
||
|
||
Write-Host " ✓ Processed $subscriptionCertCount certificate(s) in subscription"
|
||
} else {
|
||
Write-Host " ℹ No App Service certificates found in this subscription"
|
||
}
|
||
|
||
$processedSubscriptions++
|
||
|
||
} catch {
|
||
Write-Host " ❌ Error processing subscription: $($_.Exception.Message)" -ForegroundColor Red
|
||
Write-Host " Please verify permissions and subscription access"
|
||
}
|
||
}
|
||
|
||
Write-Host ""
|
||
Write-Host "======================================================================================================================================================================"
|
||
Write-Host "Exporting Results and Analysis"
|
||
Write-Host "======================================================================================================================================================================"
|
||
|
||
# Export results to CSV file
|
||
Write-Host "Exporting certificate inventory to CSV file..."
|
||
try {
|
||
$Result | Export-Csv -Path $fileName -NoTypeInformation -Force
|
||
Write-Host "✓ Successfully exported $($Result.Count) certificate records to: $fileName"
|
||
} catch {
|
||
Write-Error "Failed to export results to CSV file: $($_.Exception.Message)"
|
||
throw $_
|
||
}
|
||
|
||
# Display results summary table
|
||
Write-Host ""
|
||
Write-Host "Certificate Inventory Summary:"
|
||
Write-Host "======================================================================================================================================================================"
|
||
$Result | Format-Table -AutoSize
|
||
|
||
# Generate detailed analysis and statistics
|
||
Write-Host ""
|
||
Write-Host "======================================================================================================================================================================"
|
||
Write-Host "Certificate Analysis Summary"
|
||
Write-Host "======================================================================================================================================================================"
|
||
Write-Host "Execution completed: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')"
|
||
Write-Host ""
|
||
Write-Host "Processing Statistics:"
|
||
Write-Host " Subscriptions processed: $processedSubscriptions"
|
||
Write-Host " Total certificates discovered: $totalCertificates"
|
||
Write-Host " Certificates with issues: $certificatesWithIssues"
|
||
Write-Host ""
|
||
|
||
# Analyze certificate expiration status using Health property
|
||
if ($Result.Count -gt 0) {
|
||
$expiredCerts = $Result | Where-Object { $_.Health -eq "Expired" }
|
||
$criticalCerts = $Result | Where-Object { $_.Health -eq "Critical" }
|
||
$warnCerts = $Result | Where-Object { $_.Health -eq "Warning" }
|
||
$healthyCerts = $Result | Where-Object { $_.Health -eq "Healthy" }
|
||
$errorCerts = $Result | Where-Object { $_.Health -eq "Error" }
|
||
|
||
Write-Host "Certificate Status Analysis:"
|
||
Write-Host " 🔴 Expired certificates: $($expiredCerts.Count)"
|
||
Write-Host " 🟠 Critical (< 7 days): $($criticalCerts.Count)"
|
||
Write-Host " 🟡 Warning (7-30 days): $($warnCerts.Count)"
|
||
Write-Host " ✓ Healthy (> 30 days): $($healthyCerts.Count)"
|
||
Write-Host " ❌ Error/Inaccessible: $($errorCerts.Count)"
|
||
Write-Host ""
|
||
|
||
# Display urgent action items
|
||
if ($expiredCerts.Count -gt 0 -or $criticalCerts.Count -gt 0) {
|
||
Write-Host "🚨 URGENT ACTION REQUIRED:"
|
||
if ($expiredCerts.Count -gt 0) {
|
||
Write-Host " - $($expiredCerts.Count) certificate(s) have already expired"
|
||
}
|
||
if ($criticalCerts.Count -gt 0) {
|
||
Write-Host " - $($criticalCerts.Count) certificate(s) expire within 7 days"
|
||
}
|
||
Write-Host " Review the CSV file for detailed certificate information"
|
||
}
|
||
|
||
if ($warnCerts.Count -gt 0) {
|
||
Write-Host "⚠ RENEWAL PLANNING NEEDED:"
|
||
Write-Host " - $($warnCerts.Count) certificate(s) expire within 30 days"
|
||
}
|
||
} else {
|
||
Write-Host "ℹ No certificates found across all processed subscriptions"
|
||
}
|
||
|
||
Write-Host ""
|
||
Write-Host "Output File Information:"
|
||
Write-Host " File Path: $fileName"
|
||
Write-Host " File Size: $([Math]::Round((Get-Item $fileName).Length / 1KB, 2)) KB"
|
||
Write-Host ""
|
||
Write-Host "Recommendations:"
|
||
Write-Host " - Schedule regular execution for proactive certificate monitoring"
|
||
Write-Host " - Set up alerts for certificates expiring within 30 days"
|
||
Write-Host " - Implement automated renewal processes where possible"
|
||
Write-Host " - Review and resolve any certificates with error status"
|
||
Write-Host "======================================================================================================================================================================" |