Modules/AzureDevOpsDsc.Common/Api/Functions/Private/Authentication/ManagedIdentity/Test-AzDoArcSecretFilePath.ps1

<#
.SYNOPSIS
    Tests whether a path names a legitimate Azure Arc managed-identity secret file.
 
.DESCRIPTION
    Get-AzManagedIdentityToken reads the file named in the Azure Arc 401 challenge
    (WWW-Authenticate: Basic realm=<path>) and sends its content as a Basic credential. That
    path arrives over HTTP, so before anything is read it has to be shown to be a file the Arc
    agent itself would have written. Microsoft's reference implementations apply the same two
    checks this function does:
 
      1. After [System.IO.Path]::GetFullPath the file sits directly inside the Arc tokens
         directory for the platform (see Get-AzDoArcTokensDirectory). A relative path, a
         '..' traversal, a path on another drive or share, or a nested directory all fail.
      2. The file has the .key extension.
 
    The comparison is case-insensitive on Windows and case-sensitive on Linux. Both '\' and '/'
    are treated as separators on every platform so that each branch behaves the same whichever
    host runs it; that only ever makes the check stricter.
 
.PARAMETER Path
    The path taken from the WWW-Authenticate realm.
 
.OUTPUTS
    System.Boolean
 
.EXAMPLE
    Test-AzDoArcSecretFilePath -Path 'C:\ProgramData\AzureConnectedMachineAgent\Tokens\8d2e.key'
    True
 
.EXAMPLE
    Test-AzDoArcSecretFilePath -Path 'C:\Windows\win.ini'
    False
#>

Function Test-AzDoArcSecretFilePath
{
    [CmdletBinding()]
    [OutputType([Bool])]
    param (
        [Parameter(Mandatory = $true)]
        [AllowNull()]
        [AllowEmptyString()]
        [String]
        $Path
    )

    if ([String]::IsNullOrWhiteSpace($Path))
    {
        Write-Verbose "[Test-AzDoArcSecretFilePath] The path is empty."
        return $false
    }

    $OSInfo          = Get-OperatingSystemInfo
    $tokensDirectory = Get-AzDoArcTokensDirectory

    # Resolve a path to a canonical, separator-normalised, '.'/'..'-free segment list. GetFullPath
    # does this on the host platform; the segment pass repeats it so the result is the same when
    # the platform under test is not the host.
    $resolve = {
        param ([String] $candidate)

        $full     = [System.IO.Path]::GetFullPath($candidate)
        $segments = [System.Collections.Generic.List[String]]::new()

        foreach ($segment in ($full -split '[\\/]+'))
        {
            if ($segment -eq '' -or $segment -eq '.')
            {
                continue
            }
            if ($segment -eq '..')
            {
                if ($segments.Count -gt 0)
                {
                    $segments.RemoveAt($segments.Count - 1)
                }
                continue
            }
            $segments.Add($segment)
        }

        return , $segments.ToArray()
    }

    try
    {
        $pathSegments      = & $resolve $Path
        $directorySegments = & $resolve $tokensDirectory
    }
    catch
    {
        Write-Verbose "[Test-AzDoArcSecretFilePath] The path '$Path' could not be resolved: $_"
        return $false
    }

    # The file has to sit directly inside the tokens directory.
    if ($pathSegments.Count -ne ($directorySegments.Count + 1))
    {
        Write-Verbose "[Test-AzDoArcSecretFilePath] '$Path' is not directly inside '$tokensDirectory'."
        return $false
    }

    for ($i = 0; $i -lt $directorySegments.Count; $i++)
    {
        $same = if ($OSInfo.Windows)
        {
            $pathSegments[$i] -ieq $directorySegments[$i]
        }
        else
        {
            $pathSegments[$i] -ceq $directorySegments[$i]
        }

        if (-not $same)
        {
            Write-Verbose "[Test-AzDoArcSecretFilePath] '$Path' is not inside '$tokensDirectory'."
            return $false
        }
    }

    $extension = [System.IO.Path]::GetExtension($pathSegments[-1])
    $isKeyFile = if ($OSInfo.Windows) { $extension -ieq '.key' } else { $extension -ceq '.key' }

    if (-not $isKeyFile)
    {
        Write-Verbose "[Test-AzDoArcSecretFilePath] '$Path' does not have the .key extension."
        return $false
    }

    return $true
}