Support/scripts/Get-AzsSSLEndPoints.ps1 (269 lines of code) (raw):

<# .SYNOPSIS Display certificate inventory for Public SSL EndPoints .DESCRIPTION Probes SSL endpoints of an Azure Stack deployment to gather external certificate inventory. It then displays certificate inventory for Public SSL EndPoints. Endpoints not accessible will return blank information. User can optionally add ADFS, AppServices, SQLAdapter and MySQLAdapter. User can exclude certain endpoints also. SSL Endpoints tested are documented here: https://docs.microsoft.com/en-gb/azure/azure-stack/azure-stack-integrate-endpoints#ports-and-protocols-inbound .EXAMPLE PS C:\> .\Get-AzsSSLEndPoints.ps1 -FQDN "east.azurestack.contoso.com" Seeds public endpoints with east.azurestack.contoso.com and attempts gather certificate inventory from each endpoint .EXAMPLE PS C:\> .\Get-AzsSSLEndPoints.ps1 -FQDN "east.azurestack.contoso.com" -exclude adminvault Seeds public endpoints with east.azurestack.contoso.com and attempts gather certificate inventory from each endpoint, except adfs, graph and adminvault. .EXAMPLE PS C:\> .\Get-AzsSSLEndPoints.ps1 -FQDN "east.azurestack.contoso.com" -UsePaaS -exclude AppServices Seeds public endpoints with east.azurestack.contoso.com and attempts gather certificate inventory from each endpoint includes PaaS endpoints except for AppServices. .EXAMPLE PS C:\> .\Get-AzsSSLEndPoints.ps1 -FQDN "east.azurestack.contoso.com" -ExpiringInDays 30 Seeds public endpoints with east.azurestack.contoso.com and attempts gather certificate inventory from each endpoint and only warns if expiry is in less than 30 days. .PARAMETER FQDN String. Specifies FQDN (region.domain.com) of the AzureStack deployment. .PARAMETER UseADFS Switch. Add ADFS and Graph services to be scanned .PARAMETER UsePaaS Switch. Add PaaS services to be scanned .PARAMETER exclude String Array. Specifies endpoints that should be skipped. Valid (any/all) values 'adminportal','adminmanagement','queue','table','blob','adminvault','adfs','graph','mysqladapter','sqladapter','appservice' Tenant facing services; portal, management and vault, cannot be excluded. If these are not reachable the script will not error, but the output will be empty. .PARAMETER ExpiringInDays Integer. Optional parameter for user defined threshold for expiration warning. .PARAMETER PassThru Switch. Returns a custom object (PSCustomObject) that contains the test results. .OUTPUTS PSCustomObject .NOTES When checking wildcard endpoints, the script generates a random GUID to test against, this ensures uniqueness, the GUID is then replaced with 'certtest' so output to the screen is more tidy. #> param ( [Parameter(Mandatory = $true, HelpMessage = "Provide the FQDN for azure stack environment e.g. regionname.domain.com")] [string] $FQDN, [Parameter(Mandatory = $false, HelpMessage = "Provide number of days (default 90) warning for expiring certificates.")] [int] $ExpiringInDays = 90, [Parameter(Mandatory = $false, HelpMessage = "Include ADFS and Graph services")] [switch] $UseADFS, [Parameter(Mandatory = $false, HelpMessage = "Include PaaS services")] [switch] $UsePaaS, [Parameter(Mandatory = $false, HelpMessage = "Optionally remove services")] [ValidateSet('adminportal', 'adminmanagement', 'queue', 'table', 'blob', 'adminvault', 'adfs', 'graph', 'mysqladapter', 'sqladapter', 'appservice')] [string[]] $exclude, [Parameter(Mandatory = $false, HelpMessage = "Return PSObject")] [switch] $PassThru, [Parameter(Mandatory = $false, HelpMessage = "Include all ports")] [switch] $AllPorts ) # endpoint data if(!$AllPorts) { $allendPoints = @( 443 | ForEach-Object {"adminportal.{0}:{1}" -f $FQDN, $PSITEM} 443 | ForEach-Object {"portal.{0}:{1}" -f $FQDN, $PSITEM} 443 | Foreach-Object {"management.{0}:{1}" -f $FQDN, $PSITEM} 443 | Foreach-Object {"adminmanagement.{0}:{1}" -f $FQDN, $PSITEM} ) } else { $allendPoints = @( 443,12495,12499,12646,12647,12648,12649,12650,13001,13003,13010,13011,13012,13020,13021,13026,30015 | ForEach-Object {"adminportal.{0}:{1}" -f $FQDN, $PSITEM} 443,12495,12649,13001,13010,13011,13012,13020,13021,30015,13003 | ForEach-Object {"portal.{0}:{1}" -f $FQDN, $PSITEM} 443,30024 | Foreach-Object {"management.{0}:{1}" -f $FQDN, $PSITEM} 443,30024 | Foreach-Object {"adminmanagement.{0}:{1}" -f $FQDN, $PSITEM} ) } $allendPoints += @( 443 | Foreach-Object {"{0}.queue.{1}:{2}" -f (new-guid),$FQDN,$PSITEM} 443 | Foreach-Object {"{0}.table.{1}:{2}" -f (new-guid),$FQDN,$PSITEM} 443 | Foreach-Object {"{0}.blob.{1}:{2}" -f (new-guid),$FQDN,$PSITEM} "$(new-guid).vault.$FQDN" "$(new-guid).adminvault.$FQDN" 443 | Foreach-Object {"{0}.hosting.{1}:{2}" -f (new-guid),$FQDN,$PSITEM} 443 | Foreach-Object {"{0}.adminhosting.{1}:{2}" -f (new-guid),$FQDN,$PSITEM} ) if ($UseADFS) { $allendPoints += @( "adfs.$FQDN" "graph.$FQDN" ) } if ($UsePaaS) { $allendPoints += @( 44300 | ForEach-Object {"mysqladapter.dbadapter.{0}:{1}" -f $FQDN,$PSITEM} 44300 | ForEach-Object {"sqladapter.dbadapter.{0}:{1}" -f $FQDN,$PSITEM} 44300 | ForEach-Object {"sqlrp.dbadapter.{0}:{1}" -f $FQDN,$PSITEM} "sso.appservice.$FQDN" 443 | ForEach-Object {"$(new-guid).appservice.{0}:{1}" -f $FQDN, $PSITEM} "$(new-guid).scm.appservice.$FQDN" "$(new-guid).sso.appservice.$FQDN" 443,44300 | ForEach-Object {"api.appservice.{0}:{1}" -f $FQDN, $PSITEM} 443 | ForEach-Object {"$(new-guid).azsacr.{0}:{1}" -f $FQDN, $PSITEM} ) } function Get-ThumbprintMask { [cmdletbinding()] [OutputType([string])] Param ([Parameter(ValueFromPipelinebyPropertyName=$True)]$thumbprint) Begin { $thumbprintMasks = @() } Process { $thumbprintMasks += foreach ($thumb in $thumbprint) { try { if (($thumb.length - 12) -gt 0) { $firstSix = $thumb.Substring(0,6) $lastSix = $thumb.Substring(($thumb.length - 6),6) $middleN = '*' * ($thumb.length - 12) $thumbprintMask = '{0}{1}{2}' -f $firstSix,$middleN, $lastSix } else { throw ("Error applying thumbprint mask from thumbprint starting with {0} and length of {1}" -f $thumbprint.Substring(0,10),$thumbprint.Length) } } catch { $_.exception } $thumbprintMask } } End { $thumbprintMasks -join ',' } } function Test-CertificateReuse { param ($results) # Here we are looking for duplicate thumbprints that are used on more than one fqdn if ($dups = $results | Where-Object { $null -ne $_.name -and $null -ne $_.thumbprint } | Group-Object thumbprint | Where-Object Count -gt 1 | Where-Object { $_.Group | Group-Object fqdn | Where-Object Count -eq 1 }) { Write-Host "`n" Write-Warning -Message "Certificate Reuse Detected. We recommend using separate certificates for each endpoint." foreach ($_dup in $dups) { $names = $_dup.Group.FQDN | Foreach-Object {$PSITEM.split(':')[0]} | Get-Unique Write-Host ("`nCertificate {0} is configured for use on the following endpoint(s): " -f $_dup.name) $names | Foreach-Object {Write-Host ("`t{0}" -f ($_ -replace 'testcert.'))} } Write-Host "`n" } else { Write-Host "`nNo Certificate Reuse Detected." -ForegroundColor Green } } #Function to display colour-coded table function Write-Table { param ([Parameter(ValueFromPipeline = $True)] $object, [int]$padright = 15 ) process { foreach ($obj in $object) { Write-Host "`n" foreach ($key in $obj.PSObject.Properties.Name) { Write-Host ("{0}: " -f $key).PadRight($padright) -NoNewline Write-Host (": " -f $key) -NoNewline if ($obj.State -like 'EXPIRING') { Write-Host (" {0}" -f $obj.$key) -ForegroundColor Yellow } elseif ($obj.State -like 'EXPIRED') { Write-Host (" {0}" -f $obj.$key) -ForegroundColor Red } else { Write-Host (" {0}" -f $obj.$key) -foreground Green } } } } } # filter on exclude if ($exclude) { $excludelist = $exclude -join '|' $endPoints = $allendPoints.Where( {$_ -notmatch $excludeList}) } else { $endPoints = $allendPoints } $results = New-Object System.Collections.ArrayList $i = 1 foreach ($endPoint in $endPoints) { [int]$percentageComplete = $i / $endPoints.count * 100 Write-Progress -Activity "Testing Azure Stack Endpoints" -Status "$percentageComplete% Complete:" -PercentComplete $percentageComplete -CurrentOperation $endPoint $result = New-Object -TypeName PSCustomObject -Property @{ Name = $endPoint FQDN = $null Thumbprint = $null Subject = $null Expires = $null Issuer = $null Notes = $null State = $null } if (Resolve-DnsName -Name $endPoint.split(':')[0] -ErrorAction SilentlyContinue -QuickTimeout) { #try and retrieve certificate inventory from SSL endpoint $SSLEndPoint = "https://{0}" -f $endPoint try { $null = Invoke-WebRequest $SSLEndPoint -TimeoutSec 3 } catch { $ConnectionError = $_.exception.message } #create service connection point to the target SSL endpoint $servicePoint = [System.Net.ServicePointManager]::FindServicePoint($SSLEndPoint) # Cosmetic: change guid to testcert to ensure uniqueness in the call but tidy the screen output. $pattern = '(\{|\()?[A-Za-z0-9]{4}([A-Za-z0-9]{4}\-?){4}[A-Za-z0-9]{12}(\}|\()?' $endPoint = [regex]::replace($endPoint, $pattern, "testcert") $result.Name = $endPoint $result.fqdn = ($endpoint -split ':')[0] if ($servicePoint.Certificate) { $result.Thumbprint = Get-ThumbprintMask -thumbprint $servicePoint.Certificate.GetCertHashString() $result.Expires = $servicePoint.Certificate.GetExpirationDateString() $result.Issuer = $servicePoint.Certificate.Issuer $result.Subject = $servicePoint.Certificate.Subject # calculate expiry days and check against threshold. $actualExpiryDays = ((Get-Date $result.Expires) - (Get-Date)).Days if ((Get-Date $result.Expires) -lt (Get-Date)) { $result.notes = "Renew Certificate. Certificate expired {0}" -f $result.Expires $result.State = "EXPIRED" } elseif ($actualExpiryDays -lt $ExpiringInDays) { $result.notes = "CONSIDER RENEWING: Certificate expires in {0} days" -f $actualExpiryDays $result.State = "EXPIRING" } else { $result.notes = "Certificate expires in {0} days" -f $actualExpiryDays $result.State = "OK" } } else { $result.notes = "Unable to connect to SSL endpoint {0}. Error: {1}" -f $endPoint, $ConnectionError } } else { $result.notes = "Cannot resolve name {0}" -f $endPoint } $i++ $results.Add($result) | Out-Null } # if the PSEdition is desktop and the user doesn't use passthru colour-code the output in a table # otherwise just return the object if ($PSEdition -eq 'Desktop' -AND -not $passThru) { $results | Write-Table -padright 15 Test-CertificateReuse -results $results } else { $results }