From 5779b7e1ed0a2e42b3ab8ee404d42fb6a3928470 Mon Sep 17 00:00:00 2001 From: Lukas Sassl Date: Wed, 27 Nov 2024 18:54:53 +0100 Subject: [PATCH 1/3] Improve hybrid transport certificate checks --- .build/cspell-words.txt | 1 + ...ke-AnalyzerFrequentConfigurationIssues.ps1 | 21 +-- .../Invoke-AnalyzerHybridInformation.ps1 | 132 ++++++++++-------- .../Get-ExchangeConnectors.ps1 | 1 + 4 files changed, 86 insertions(+), 69 deletions(-) diff --git a/.build/cspell-words.txt b/.build/cspell-words.txt index fa3091ccc6..3a87eaac45 100644 --- a/.build/cspell-words.txt +++ b/.build/cspell-words.txt @@ -115,6 +115,7 @@ nupkg odata onmicrosoft onprem +onmschina OutlookiOS Perflib perfmon diff --git a/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerFrequentConfigurationIssues.ps1 b/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerFrequentConfigurationIssues.ps1 index 12cd4301b0..54d61eb48a 100644 --- a/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerFrequentConfigurationIssues.ps1 +++ b/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerFrequentConfigurationIssues.ps1 @@ -299,8 +299,8 @@ function Invoke-AnalyzerFrequentConfigurationIssues { $sendConnectors = $exchangeInformation.ExchangeConnectors | Where-Object { $_.ConnectorType -eq "Send" } foreach ($sendConnector in $sendConnectors) { - $smartHostMatch = ($sendConnector.SmartHosts -like "*.mail.protection.outlook.com").Count -gt 0 - $dnsMatch = $sendConnector.SmartHosts -eq 0 -and ($sendConnector.AddressSpaces.Address -like "*.mail.onmicrosoft.com").Count -gt 0 + $smartHostMatch = ($sendConnector.SmartHosts -match "^[^.]+\.mail\.protection\.(outlook\.com|partner\.outlook\.cn|office365\.us)$").Count -gt 0 + $dnsMatch = $sendConnector.SmartHosts -eq 0 -and ($sendConnector.AddressSpaces.Address -match "^[^.]+\.mail\.(onmicrosoft\.com|partner\.onmschina\.cn|onmicrosoft\.us)$").Count -gt 0 if ($dnsMatch -or $smartHostMatch) { $exoConnector.Add($sendConnector) @@ -315,12 +315,17 @@ function Invoke-AnalyzerFrequentConfigurationIssues { $showMoreInfo = $false foreach ($connector in $exoConnector) { - # Misconfigured connector is if TLSCertificateName is not set or CloudServicesMailEnabled not set to true - if ($connector.CloudEnabled -eq $false -or - $connector.CertificateDetails.TlsCertificateNameStatus -eq "TlsCertificateNameEmpty") { + + # If CloudServiceMailEnabled is not set to true it means the connector is misconfigured + # If no Fqdn is set on the connector, the Fqdn of the computer is used to perform best matching certificate selection + # There is a risk that the Fqdn is an internal one (e.g., server.contoso.local) which will lead to a broken hybrid mail flow in case that TlsCertificateName is not set + if (($connector.CloudEnabled -eq $false) -or + ($null -eq $connector.Fqdn -and + $connector.CertificateDetails.TlsCertificateNameStatus -eq "TlsCertificateNameEmpty")) { $params = $baseParams + @{ Name = "Send Connector - $($connector.Identity.ToString())" - Details = "Misconfigured to send authenticated internal mail to M365." + + Details = "Misconfigured to send authenticated internal mail to M365" + + "`r`n`t`t`tFqdn set: $($null -ne $connector.Fqdn)" + "`r`n`t`t`tCloudServicesMailEnabled: $($connector.CloudEnabled)" + "`r`n`t`t`tTLSCertificateName set: $($connector.CertificateDetails.TlsCertificateNameStatus -ne "TlsCertificateNameEmpty")" DisplayCustomTabNumber = 2 @@ -342,11 +347,11 @@ function Invoke-AnalyzerFrequentConfigurationIssues { $showMoreInfo = $true } - if ($connector.TlsDomain -ne "mail.protection.outlook.com" -and + if ($connector.TlsDomain -notmatch "^mail\.protection\.(outlook\.com|partner\.outlook\.cn|office365\.us)$" -and $connector.TlsAuthLevel -eq "DomainValidation") { $params = $baseParams + @{ Name = "Send Connector - $($connector.Identity.ToString())" - Details = "TLSDomain not set to mail.protection.outlook.com" + Details = "TLSDomain not set to service domain (e.g.,mail.protection.outlook.com)" DisplayCustomTabNumber = 2 DisplayWriteType = "Yellow" } diff --git a/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerHybridInformation.ps1 b/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerHybridInformation.ps1 index 2017e3e329..9e54e11a71 100644 --- a/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerHybridInformation.ps1 +++ b/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerHybridInformation.ps1 @@ -354,6 +354,13 @@ function Invoke-AnalyzerHybridInformation { } } else { $cloudConnectorTlsCertificateName = "Not set" + + # Check if the server is configured as sending or receiving transport server - if it is, the certificate used for hybrid mail flow must exist on the machine + $certificateShouldExistOnServer = $getHybridConfiguration.SendingTransportServers.DistinguishedName -contains $exchangeInformation.GetExchangeServer.DistinguishedName -or + $getHybridConfiguration.ReceivingTransportServers.DistinguishedName -contains $exchangeInformation.GetExchangeServer.DistinguishedName + + Write-Verbose "Server is configured for hybrid mailflow and the transport certificate should exist on this server? $certificateShouldExistOnServer" + if ($null -ne $connector.CertificateDetails.TlsCertificateName) { $cloudConnectorTlsCertificateName = $connector.CertificateDetails.TlsCertificateName } @@ -365,87 +372,90 @@ function Invoke-AnalyzerHybridInformation { } Add-AnalyzedResultInformation @params - $params = $baseParams + @{ - Name = "Certificate Found On Server" - Details = $connector.CertificateDetails.CertificateMatchDetected - DisplayWriteType = $cloudConnectorWriteType - } - Add-AnalyzedResultInformation @params - - if ($connector.CertificateDetails.TlsCertificateNameStatus -eq "TlsCertificateNameEmpty") { + # Don't perform the following checks if the server is not a sending or receiving transport server configured for hybrid mail flow (there is a high chance that the certificate didn't exist which is by design) + if ($certificateShouldExistOnServer) { $params = $baseParams + @{ - Details = "There is no 'TlsCertificateName' configured for this cloud mail enabled connector.`r`n`t`tThis will cause mail flow issues in hybrid scenarios. More information: https://aka.ms/HC-HybridConnector" - DisplayWriteType = $cloudConnectorWriteType - DisplayCustomTabNumber = 2 - } - Add-AnalyzedResultInformation @params - } elseif ($connector.CertificateDetails.CertificateMatchDetected -eq $false) { - $params = $baseParams + @{ - Details = "The configured 'TlsCertificateName' was not found on the server.`r`n`t`tThis may cause mail flow issues. More information: https://aka.ms/HC-HybridConnector" - DisplayWriteType = $cloudConnectorWriteType - DisplayCustomTabNumber = 2 + Name = "Certificate Found On Server" + Details = $connector.CertificateDetails.CertificateMatchDetected + DisplayWriteType = $cloudConnectorWriteType } Add-AnalyzedResultInformation @params - } else { - Add-AnalyzedResultInformation -Name "Certificate Thumbprint(s)" @baseParams - foreach ($thumbprint in $($connector.CertificateDetails.CertificateLifetimeInfo).keys) { + if ($connector.CertificateDetails.TlsCertificateNameStatus -eq "TlsCertificateNameEmpty") { $params = $baseParams + @{ - Details = $thumbprint + Details = "There is no 'TlsCertificateName' configured for this cloud mail enabled connector.`r`n`t`tThis will cause mail flow issues in hybrid scenarios. More information: https://aka.ms/HC-HybridConnector" + DisplayWriteType = $cloudConnectorWriteType DisplayCustomTabNumber = 2 } Add-AnalyzedResultInformation @params - } - - Add-AnalyzedResultInformation -Name "Lifetime In Days" @baseParams - - foreach ($thumbprint in $($connector.CertificateDetails.CertificateLifetimeInfo).keys) { - switch ($($connector.CertificateDetails.CertificateLifetimeInfo)[$thumbprint]) { - { ($_ -ge 60) } { $certificateLifetimeWriteType = "Green"; break } - { ($_ -ge 30) } { $certificateLifetimeWriteType = "Yellow"; break } - default { $certificateLifetimeWriteType = "Red" } - } - + } elseif ($connector.CertificateDetails.CertificateMatchDetected -eq $false) { $params = $baseParams + @{ - Details = ($connector.CertificateDetails.CertificateLifetimeInfo)[$thumbprint] - DisplayWriteType = $certificateLifetimeWriteType + Details = "The configured 'TlsCertificateName' was not found on the server.`r`n`t`tThis may cause mail flow issues. More information: https://aka.ms/HC-HybridConnector" + DisplayWriteType = $cloudConnectorWriteType DisplayCustomTabNumber = 2 } Add-AnalyzedResultInformation @params - } + } else { + Add-AnalyzedResultInformation -Name "Certificate Thumbprint(s)" @baseParams + + foreach ($thumbprint in $($connector.CertificateDetails.CertificateLifetimeInfo).keys) { + $params = $baseParams + @{ + Details = $thumbprint + DisplayCustomTabNumber = 2 + } + Add-AnalyzedResultInformation @params + } - $connectorCertificateMatchesHybridCertificate = $false - $connectorCertificateMatchesHybridCertificateWritingType = "Yellow" - if (($connector.CertificateDetails.TlsCertificateSet) -and - (-not([System.String]::IsNullOrEmpty($getHybridConfiguration.TlsCertificateName))) -and - ($connector.CertificateDetails.TlsCertificateName -eq $getHybridConfiguration.TlsCertificateName)) { - $connectorCertificateMatchesHybridCertificate = $true - $connectorCertificateMatchesHybridCertificateWritingType = "Green" - } + Add-AnalyzedResultInformation -Name "Lifetime In Days" @baseParams + + foreach ($thumbprint in $($connector.CertificateDetails.CertificateLifetimeInfo).keys) { + switch ($($connector.CertificateDetails.CertificateLifetimeInfo)[$thumbprint]) { + { ($_ -ge 60) } { $certificateLifetimeWriteType = "Green"; break } + { ($_ -ge 30) } { $certificateLifetimeWriteType = "Yellow"; break } + default { $certificateLifetimeWriteType = "Red" } + } + + $params = $baseParams + @{ + Details = ($connector.CertificateDetails.CertificateLifetimeInfo)[$thumbprint] + DisplayWriteType = $certificateLifetimeWriteType + DisplayCustomTabNumber = 2 + } + Add-AnalyzedResultInformation @params + } - $params = $baseParams + @{ - Name = "Certificate Matches Hybrid Certificate" - Details = $connectorCertificateMatchesHybridCertificate - DisplayWriteType = $connectorCertificateMatchesHybridCertificateWritingType - } - Add-AnalyzedResultInformation @params + $connectorCertificateMatchesHybridCertificate = $false + $connectorCertificateMatchesHybridCertificateWritingType = "Yellow" + if (($connector.CertificateDetails.TlsCertificateSet) -and + (-not([System.String]::IsNullOrEmpty($getHybridConfiguration.TlsCertificateName))) -and + ($connector.CertificateDetails.TlsCertificateName -eq $getHybridConfiguration.TlsCertificateName)) { + $connectorCertificateMatchesHybridCertificate = $true + $connectorCertificateMatchesHybridCertificateWritingType = "Green" + } - if (($connector.CertificateDetails.TlsCertificateNameStatus -eq "TlsCertificateNameSyntaxInvalid") -or - (($connector.CertificateDetails.GoodTlsCertificateSyntax -eq $false) -and - ($null -ne $connector.CertificateDetails.TlsCertificateName))) { $params = $baseParams + @{ - Name = "TlsCertificateName Syntax Invalid" - Details = "True" - DisplayWriteType = $cloudConnectorWriteType + Name = "Certificate Matches Hybrid Certificate" + Details = $connectorCertificateMatchesHybridCertificate + DisplayWriteType = $connectorCertificateMatchesHybridCertificateWritingType } Add-AnalyzedResultInformation @params - $params = $baseParams + @{ - Details = "The correct syntax is: 'X.500IssuerX.500Subject'" - DisplayWriteType = $cloudConnectorWriteType - DisplayCustomTabNumber = 2 + if (($connector.CertificateDetails.TlsCertificateNameStatus -eq "TlsCertificateNameSyntaxInvalid") -or + (($connector.CertificateDetails.GoodTlsCertificateSyntax -eq $false) -and + ($null -ne $connector.CertificateDetails.TlsCertificateName))) { + $params = $baseParams + @{ + Name = "TlsCertificateName Syntax Invalid" + Details = "True" + DisplayWriteType = $cloudConnectorWriteType + } + Add-AnalyzedResultInformation @params + + $params = $baseParams + @{ + Details = "The correct syntax is: 'X.500IssuerX.500Subject'" + DisplayWriteType = $cloudConnectorWriteType + DisplayCustomTabNumber = 2 + } + Add-AnalyzedResultInformation @params } - Add-AnalyzedResultInformation @params } } } diff --git a/Diagnostics/HealthChecker/DataCollection/ExchangeInformation/Get-ExchangeConnectors.ps1 b/Diagnostics/HealthChecker/DataCollection/ExchangeInformation/Get-ExchangeConnectors.ps1 index 9ead7441ea..30c1842a13 100644 --- a/Diagnostics/HealthChecker/DataCollection/ExchangeInformation/Get-ExchangeConnectors.ps1 +++ b/Diagnostics/HealthChecker/DataCollection/ExchangeInformation/Get-ExchangeConnectors.ps1 @@ -30,6 +30,7 @@ function Get-ExchangeConnectors { $exchangeFactoryConnectorReturnObject = [PSCustomObject]@{ Identity = $ConnectorObject.Identity Name = $ConnectorObject.Name + Fqdn = $ConnectorObject.Fqdn Enabled = $ConnectorObject.Enabled CloudEnabled = $false ConnectorType = $null From 90732fefa6296822e6d0cb2e608a6e14995a5925 Mon Sep 17 00:00:00 2001 From: Lukas Sassl Date: Thu, 28 Nov 2024 09:38:49 +0100 Subject: [PATCH 2/3] Minor adjustments to fix cSpell detection --- .build/cspell-words.txt | 1 - .../Invoke-AnalyzerFrequentConfigurationIssues.ps1 | 14 ++++++++++---- .../Analyzer/Invoke-AnalyzerHybridInformation.ps1 | 8 ++++---- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/.build/cspell-words.txt b/.build/cspell-words.txt index 3a87eaac45..fa3091ccc6 100644 --- a/.build/cspell-words.txt +++ b/.build/cspell-words.txt @@ -115,7 +115,6 @@ nupkg odata onmicrosoft onprem -onmschina OutlookiOS Perflib perfmon diff --git a/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerFrequentConfigurationIssues.ps1 b/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerFrequentConfigurationIssues.ps1 index 54d61eb48a..4a9a2ce2d0 100644 --- a/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerFrequentConfigurationIssues.ps1 +++ b/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerFrequentConfigurationIssues.ps1 @@ -23,6 +23,12 @@ function Invoke-AnalyzerFrequentConfigurationIssues { $tcpKeepAlive = $osInformation.RegistryValues.TCPKeepAlive $organizationInformation = $HealthServerObject.OrganizationInformation + # cSpell:disable + $eopDomainRegExPattern = "^[^.]+\.mail\.protection\.(outlook\.com|partner\.outlook\.cn|office365\.us)$" + $remoteRoutingDomainRegExPattern = "^[^.]+\.mail\.(onmicrosoft\.com|partner\.onmschina\.cn|onmicrosoft\.us)$" + $serviceDomainRegExPattern = "^mail\.protection\.(outlook\.com|partner\.outlook\.cn|office365\.us)$" + # cSpell:enable + $baseParams = @{ AnalyzedInformation = $AnalyzeResults DisplayGroupingKey = (Get-DisplayResultsGroupingKey -Name "Frequent Configuration Issues" -DisplayOrder $Order) @@ -299,8 +305,8 @@ function Invoke-AnalyzerFrequentConfigurationIssues { $sendConnectors = $exchangeInformation.ExchangeConnectors | Where-Object { $_.ConnectorType -eq "Send" } foreach ($sendConnector in $sendConnectors) { - $smartHostMatch = ($sendConnector.SmartHosts -match "^[^.]+\.mail\.protection\.(outlook\.com|partner\.outlook\.cn|office365\.us)$").Count -gt 0 - $dnsMatch = $sendConnector.SmartHosts -eq 0 -and ($sendConnector.AddressSpaces.Address -match "^[^.]+\.mail\.(onmicrosoft\.com|partner\.onmschina\.cn|onmicrosoft\.us)$").Count -gt 0 + $smartHostMatch = ($sendConnector.SmartHosts -match $eopDomainRegExPattern).Count -gt 0 + $dnsMatch = $sendConnector.SmartHosts -eq 0 -and ($sendConnector.AddressSpaces.Address -match $remoteRoutingDomainRegExPattern).Count -gt 0 if ($dnsMatch -or $smartHostMatch) { $exoConnector.Add($sendConnector) @@ -347,11 +353,11 @@ function Invoke-AnalyzerFrequentConfigurationIssues { $showMoreInfo = $true } - if ($connector.TlsDomain -notmatch "^mail\.protection\.(outlook\.com|partner\.outlook\.cn|office365\.us)$" -and + if ($connector.TlsDomain -notmatch $serviceDomainRegExPattern -and $connector.TlsAuthLevel -eq "DomainValidation") { $params = $baseParams + @{ Name = "Send Connector - $($connector.Identity.ToString())" - Details = "TLSDomain not set to service domain (e.g.,mail.protection.outlook.com)" + Details = "TLSDomain not set to service domain (e.g.,mail.protection.outlook.com)" DisplayCustomTabNumber = 2 DisplayWriteType = "Yellow" } diff --git a/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerHybridInformation.ps1 b/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerHybridInformation.ps1 index 9e54e11a71..4090e53eff 100644 --- a/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerHybridInformation.ps1 +++ b/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerHybridInformation.ps1 @@ -24,6 +24,10 @@ function Invoke-AnalyzerHybridInformation { $exchangeInformation = $HealthServerObject.ExchangeInformation $getHybridConfiguration = $HealthServerObject.OrganizationInformation.GetHybridConfiguration + # Check if the server is configured as sending or receiving transport server - if it is, the certificate used for hybrid mail flow must exist on the machine + $certificateShouldExistOnServer = $getHybridConfiguration.SendingTransportServers.DistinguishedName -contains $exchangeInformation.GetExchangeServer.DistinguishedName -or + $getHybridConfiguration.ReceivingTransportServers.DistinguishedName -contains $exchangeInformation.GetExchangeServer.DistinguishedName + if ($exchangeInformation.BuildInformation.VersionInformation.BuildVersion -ge "15.0.0.0" -and $null -ne $getHybridConfiguration) { @@ -355,10 +359,6 @@ function Invoke-AnalyzerHybridInformation { } else { $cloudConnectorTlsCertificateName = "Not set" - # Check if the server is configured as sending or receiving transport server - if it is, the certificate used for hybrid mail flow must exist on the machine - $certificateShouldExistOnServer = $getHybridConfiguration.SendingTransportServers.DistinguishedName -contains $exchangeInformation.GetExchangeServer.DistinguishedName -or - $getHybridConfiguration.ReceivingTransportServers.DistinguishedName -contains $exchangeInformation.GetExchangeServer.DistinguishedName - Write-Verbose "Server is configured for hybrid mailflow and the transport certificate should exist on this server? $certificateShouldExistOnServer" if ($null -ne $connector.CertificateDetails.TlsCertificateName) { From e2140d1e0542e7d714f372de5cbc1ce0d24fe886 Mon Sep 17 00:00:00 2001 From: Lukas Sassl Date: Thu, 28 Nov 2024 10:29:19 +0100 Subject: [PATCH 3/3] Make cSpell happy again --- .../HealthChecker/Analyzer/Invoke-AnalyzerHybridInformation.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerHybridInformation.ps1 b/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerHybridInformation.ps1 index 4090e53eff..8845421e27 100644 --- a/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerHybridInformation.ps1 +++ b/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerHybridInformation.ps1 @@ -359,7 +359,7 @@ function Invoke-AnalyzerHybridInformation { } else { $cloudConnectorTlsCertificateName = "Not set" - Write-Verbose "Server is configured for hybrid mailflow and the transport certificate should exist on this server? $certificateShouldExistOnServer" + Write-Verbose "Server is configured for hybrid mail flow and the transport certificate should exist on this server? $certificateShouldExistOnServer" if ($null -ne $connector.CertificateDetails.TlsCertificateName) { $cloudConnectorTlsCertificateName = $connector.CertificateDetails.TlsCertificateName