tests/Test-Assessment.27002.ps1

<#
.SYNOPSIS
    TLS inspection certificates have sufficient validity period to prevent service disruption
 
.DESCRIPTION
    Verifies that TLS inspection certificates in Global Secure Access have more than 90 days
    until expiration and no certificates have status of expiring or expired.
 
.NOTES
    Test ID: 27002
    Pillar: Network
    Risk Level: High
    SFI Pillar: Protect networks
#>


function Test-Assessment-27002 {
    [ZtTest(
        Category = 'Global Secure Access',
        ImplementationCost = 'Low',
        MinimumLicense = ('Entra_Premium_Internet_Access'),
        Pillar = 'Network',
        RiskLevel = 'High',
        SfiPillar = 'Protect networks',
        TenantType = ('Workforce', 'External'),
        TestId = '27002',
        Title = 'TLS inspection certificates have sufficient validity period to prevent service disruption',
        UserImpact = 'High'
    )]
    [CmdletBinding()]
    param()

    #region Data Collection
    Write-PSFMessage '🟦 Start' -Tag Test -Level VeryVerbose
    $activity = 'Checking TLS inspection certificate validity'

    # Prerequisite check: Verify TLS inspection is configured
    Write-ZtProgress -Activity $activity -Status 'Checking if TLS inspection is configured'

    $tlsInspectionPolicies = $null
    $errorMsg = $null
    try {
        $tlsInspectionPolicies = Invoke-ZtGraphRequest -RelativeUri 'networkAccess/tlsInspectionPolicies' -ApiVersion beta -ErrorAction Stop
    }
    catch {
        $errorMsg = $_
        Write-PSFMessage "Unable to retrieve TLS inspection policies: $errorMsg" -Level Warning
    }

    # If no TLS inspection policies exist, skip the test
    if (-not $errorMsg -and (-not $tlsInspectionPolicies -or $tlsInspectionPolicies.Count -eq 0)) {
        Write-PSFMessage 'TLS inspection not configured - skipping test.' -Tag Test -Level Verbose
        Add-ZtTestResultDetail -SkippedBecause NotApplicable
        return
    }

    # Q1: Retrieve all TLS inspection certificates
    # Note: The Prefer header is required to get actual status values instead of unknownFutureValue
    $certificates = $null
    $results = @()
    $evaluatedResults = @()
    $totalCount = 0
    $activeCount = 0
    $expiringWithin90Days = 0
    $expiredCount = 0

    if (-not $errorMsg) {
        Write-ZtProgress -Activity $activity -Status 'Retrieving TLS inspection certificates'

        try {
            $certificates = Invoke-ZtGraphRequest -RelativeUri 'networkAccess/tls/externalCertificateAuthorityCertificates' -ApiVersion beta -Headers @{ 'Prefer' = 'include-unknown-enum-members' } -ErrorAction Stop
        }
        catch {
            $errorMsg = $_
            Write-PSFMessage "Unable to retrieve TLS inspection certificates: $errorMsg" -Level Warning
        }
    }

    # If no certificates exist, TLS inspection policies exist but no certificates uploaded yet - Pass
    if (-not $errorMsg -and (-not $certificates -or $certificates.Count -eq 0)) {
        Write-PSFMessage 'TLS inspection policies exist but no certificates uploaded yet.' -Tag Test -Level Verbose
        $testResultMarkdown = "TLS inspection policies are configured but no certificates have been uploaded yet."
        Add-ZtTestResultDetail -TestId '27002' -Title 'TLS inspection certificates have sufficient validity period to prevent service disruption' -Status $true -Result $testResultMarkdown
        return
    }

    # Process certificates and calculate expiration details
    if (-not $errorMsg) {
        $currentDate = Get-Date
        $warningThresholdDays = 90
        # Skip certificates that are not actively in use (csrGenerated, enrolling, disabled)
        $skipStatuses = @('csrGenerated', 'enrolling', 'disabled')

        foreach ($cert in $certificates) {
            $isSkipped = $cert.status -in $skipStatuses

            $expirationDate = if ($cert.validity.endDateTime) {
                [datetime]$cert.validity.endDateTime
            }
            else {
                $null
            }

            $daysUntilExpiration = if ($expirationDate) {
                [math]::Floor(($expirationDate - $currentDate).TotalDays)
            }
            else {
                $null
            }

            # Determine if renewal is required
            $renewalRequired = $false
            if (-not $isSkipped) {
                if ($cert.status -in @('expiring', 'expired')) {
                    $renewalRequired = $true
                }
                elseif ($cert.status -in @('active', 'enabled') -and $null -ne $daysUntilExpiration -and $daysUntilExpiration -le $warningThresholdDays) {
                    $renewalRequired = $true
                }
            }

            $results += [PSCustomObject]@{
                Id                  = $cert.id
                Name                = $cert.name
                CommonName          = $cert.commonName
                OrganizationName    = $cert.organizationName
                Status              = $cert.status
                ExpirationDate      = $expirationDate
                DaysUntilExpiration = $daysUntilExpiration
                RenewalRequired     = $renewalRequired
                IsSkipped           = $isSkipped
            }
        }

        # Calculate statistics (only for non-skipped certificates)
        $evaluatedResults = @($results | Where-Object { -not $_.IsSkipped })
        $totalCount = $results.Count
        $activeCount = @($evaluatedResults | Where-Object { $_.Status -in @('active', 'enabled') }).Count
        $expiringWithin90Days = @($evaluatedResults | Where-Object { $_.RenewalRequired -and $_.Status -in @('active', 'enabled') }).Count
        $expiredCount = @($evaluatedResults | Where-Object { $_.Status -eq 'expired' }).Count
    }
    #endregion Data Collection

    #region Assessment Logic
    $passed = $false

    # Fail if there was an error retrieving data
    if ($errorMsg) {
        $passed = $false
        $testResultMarkdown = "❌ Unable to retrieve TLS inspection certificates due to error:`n`n$errorMsg`n`n%TestResult%"
    }
    else {
        # Pass if no evaluated certificates require renewal
        $certificatesRequiringRenewal = @($evaluatedResults | Where-Object { $_.RenewalRequired })
        if ($certificatesRequiringRenewal.Count -eq 0) {
            $passed = $true
            $testResultMarkdown = "✅ All TLS inspection certificates have more than 90 days until expiration.`n`n%TestResult%"
        }
        else {
            $testResultMarkdown = "❌ TLS inspection certificate requires renewal; expiration is within 90 days or certificate status indicates expiring/expired.`n`n%TestResult%"
        }
    }
    #endregion Assessment Logic

    #region Report Generation
    $mdInfo = ''

    # Build certificate summary list
    $tlsInspectionLink = 'https://entra.microsoft.com/#view/Microsoft_Azure_Network_Access/TLSInspectionPolicy.ReactView'

    $mdInfo += @"
 
## [TLS Inspection Certificates]($tlsInspectionLink)
 
**Summary:**
 
- Total active certificates: $activeCount
- Certificates expiring within 90 days: $expiringWithin90Days
- Certificates already expired: $expiredCount
 
"@


    # Build certificates table
    if ($totalCount -gt 0) {
        $formatTemplate = @'
 
**Certificate Details:**
 
| Certificate name | Common name | Status | Expiration date | Days until expiration | Renewal required |
| :--- | :--- | :--- | :--- | :--- | :--- |
{0}
 
'@


        $tableRows = ''

        foreach ($cert in $results | Sort-Object DaysUntilExpiration) {
            $certName = if ($cert.Name) {
                Get-SafeMarkdown -Text $cert.Name
            }
            else {
                'N/A'
            }

            $commonName = if ($cert.CommonName) {
                Get-SafeMarkdown -Text $cert.CommonName
            }
            else {
                'N/A'
            }

            $statusDisplay = switch ($cert.Status) {
                'active' { 'Active' }
                'enabled' { 'Enabled' }
                'expiring' { 'Expiring' }
                'expired' { 'Expired' }
                'csrGenerated' { 'CSR Generated' }
                'enrolling' { 'Enrolling' }
                'disabled' { 'Disabled' }
                default { $cert.Status }
            }

            $expirationDateDisplay = if ($cert.ExpirationDate) {
                $cert.ExpirationDate.ToString('yyyy-MM-dd')
            }
            else {
                'N/A'
            }

            $daysDisplay = if ($null -ne $cert.DaysUntilExpiration) {
                if ($cert.DaysUntilExpiration -lt 0) {
                    "Expired ($([math]::Abs($cert.DaysUntilExpiration)) days ago)"
                }
                else {
                    $cert.DaysUntilExpiration.ToString()
                }
            }
            else {
                'N/A'
            }

            $renewalRequired = if ($cert.RenewalRequired) { 'Yes' } else { 'No' }

            $tableRows += "| $certName | $commonName | $statusDisplay | $expirationDateDisplay | $daysDisplay | $renewalRequired |`n"
        }

        $mdInfo += $formatTemplate -f $tableRows
    }

    $testResultMarkdown = $testResultMarkdown -replace '%TestResult%', $mdInfo
    #endregion Report Generation

    $params = @{
        TestId = '27002'
        Title  = 'TLS inspection certificates have sufficient validity period to prevent service disruption'
        Status = $passed
        Result = $testResultMarkdown
    }

    Add-ZtTestResultDetail @params
}