Public/Test-HDTShareAcl.ps1

function Test-HDTShareAcl {
    <#
        .SYNOPSIS
            Judges whether the deployment account is least-privileged on the
            workspace.
 
        .DESCRIPTION
            The least-privilege mitigation, as a command rather than a paragraph:
            "least privilege is the mitigation, and it is enforced, not just
            documented ... Update-HDTBootImage runs this check and warns loudly
            when the account is over-privileged - a domain admin credential in a
            boot image is a domain compromise, and that is the failure worth
            catching."
 
            THE EXPECTED POSTURE:
 
              workspace root Read only
              Logs\ Write (or Modify)
              Captures\ Write (or Modify)
              everything else no more than Read
 
            THE FINDINGS:
 
              FullControl anywhere Critical
              write outside Logs\ and Captures\ Warning
              no read at the workspace root Critical
              the account is an admin group Critical
              an ACL that could not be read Information
 
            Compliant is $true only when there is no finding above Information.
 
            IT NEVER THROWS AND IT NEVER BLOCKS A BUILD. It warns.
            An administrator whose boot image build died because the checker
            could not read one ACL is an administrator who turns the checker off,
            and then nobody is told about the domain admin credential either.
 
            IT IS PURE LOGIC. The rows come from Get-HDTShareAccessRule, which is
            the only file in HDT that calls Get-Acl; everything judged here is
            judged against hand-written rows in the unit tests, with no share
            attached.
 
            The folder list it recognises is Get-HDTWorkspacePath's closed set,
            read at run time rather than repeated here, so the workspace layout
            is written down once. docs/share-account.md names the same folders,
            and a test asserts that rather than leaving a reader to check.
 
        .PARAMETER WorkspaceRoot
            The workspace root - a local path or a UNC share. Used to build the
            path each finding names.
 
        .PARAMETER Identity
            The deployment account, as 'DOMAIN\name' or a bare name. Rules
            granted to anybody else are ignored: BUILTIN\Administrators has
            FullControl on very nearly every share there has ever been, and
            judging that would make Compliant unreachable and this check
            ignorable.
 
        .PARAMETER AccessRule
            Folder relative path -> the rows Get-HDTShareAccessRule returned for
            it. '.' or '' is the workspace root. A $null value means the ACL
            could not be read.
 
        .INPUTS
            None. This command does not accept pipeline input.
 
        .OUTPUTS
            System.Management.Automation.PSCustomObject with Compliant [bool] and
            Finding - rows of Path, Severity and Message, Critical first.
 
        .EXAMPLE
            $accessRule = @{ '.' = $rootRule; 'Logs' = $logRule }
            Test-HDTShareAcl -WorkspaceRoot '\\server\HdtShare' -Identity 'CONTOSO\svc-hdt-deploy' -AccessRule $accessRule
 
        .EXAMPLE
            $result = Test-HDTShareAcl -WorkspaceRoot $root -Identity $account -AccessRule $accessRule
            if (-not $result.Compliant) {
                $result.Finding | ForEach-Object { Write-Warning $_.Message }
            }
 
            What Update-HDTBootImage does with it: warn loudly, build anyway.
    #>

    [CmdletBinding()]
    [OutputType([pscustomobject])]
    param(
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string] $WorkspaceRoot,

        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string] $Identity,

        [Parameter(Mandatory = $true)]
        [ValidateNotNull()]
        [System.Collections.IDictionary] $AccessRule
    )

    Set-StrictMode -Version Latest
    $ErrorActionPreference = 'Stop'

    # DESIGN 2.1's layout, read from the one place it is written down.
    $kind = @((Get-Command -Name Get-HDTWorkspacePath).Parameters['Kind'].Attributes |
            Where-Object { $_ -is [System.Management.Automation.ValidateSetAttribute] } |
            ForEach-Object { $_.ValidValues })

    $writableFolder = @('Logs', 'Captures')
    $adminGroupPattern = 'Domain Admins|Enterprise Admins|Administrators'

    $finding = New-Object -TypeName System.Collections.ArrayList

    $add = {
        param([string] $Path, [string] $Severity, [string] $Message)

        [void] $finding.Add([pscustomobject] @{
                Path     = $Path
                Severity = $Severity
                Message  = $Message
            })
    }

    # -- which rows belong to the deployment account --------------------------

    $bareIdentity = $Identity
    if ($Identity.Contains('\')) { $bareIdentity = $Identity.Substring($Identity.LastIndexOf('\') + 1) }

    $isOurs = {
        param([object] $Row)

        $rowIdentity = [string] $Row.Identity
        if ([string]::IsNullOrWhiteSpace($rowIdentity)) { return $false }

        if ($rowIdentity -eq $Identity) { return $true }

        $bareRow = $rowIdentity
        if ($rowIdentity.Contains('\')) { $bareRow = $rowIdentity.Substring($rowIdentity.LastIndexOf('\') + 1) }

        return ($bareRow -eq $bareIdentity)
    }

    # -- the account itself ---------------------------------------------------

    if ($bareIdentity -match $adminGroupPattern) {
        & $add $WorkspaceRoot 'Critical' (
            ("The deployment account is '{0}', which is an administrative group. A domain admin credential in a boot image is a domain compromise, and that is the failure worth catching. Use a dedicated account - docs/share-account.md has the steps." -f $Identity))
    }

    # -- the workspace root ---------------------------------------------------

    $rootKey = ''
    foreach ($key in @($AccessRule.Keys)) {
        $normalized = ([string] $key).Trim().Trim('\', '/')
        if (($normalized -eq '') -or ($normalized -eq '.')) { $rootKey = [string] $key }
    }

    $rootIsReadable = $false
    $rootIsUnreadable = $false

    if ($rootKey -ne '') {
        $rootRow = $AccessRule[$rootKey]
        if ($null -eq $rootRow) {
            $rootIsUnreadable = $true
        } else {
            foreach ($row in @($rootRow)) {
                if (([string] $row.Type -ne 'Allow') -or (-not (& $isOurs $row))) { continue }
                if ([string] $row.Rights -match 'Read|FullControl') { $rootIsReadable = $true }
            }
        }
    }

    if ((-not $rootIsReadable) -and (-not $rootIsUnreadable)) {
        & $add $WorkspaceRoot 'Critical' (
            ("'{0}' cannot read the workspace root '{1}'. The deployment cannot work: a booted machine reads its rules, its sequences and its images from here." -f $Identity, $WorkspaceRoot))
    }

    # -- every folder that was read ------------------------------------------

    foreach ($key in @($AccessRule.Keys)) {
        $normalized = ([string] $key).Trim().Trim('\', '/')
        $isRoot = (($normalized -eq '') -or ($normalized -eq '.'))

        if ($isRoot) {
            $path = $WorkspaceRoot
        } elseif ($kind -contains $normalized) {
            $path = Get-HDTWorkspacePath -Root $WorkspaceRoot -Kind $normalized
        } else {
            $path = [System.IO.Path]::Combine($WorkspaceRoot, $normalized)
        }

        $row = $AccessRule[$key]

        if ($null -eq $row) {
            & $add $path 'Information' (
                ("The access control list on '{0}' could not be read, so the least-privilege check could not judge it. Run the check from an account that can read it, or check it by hand." -f $path))
            continue
        }

        $mayWrite = ($isRoot -eq $false) -and ($writableFolder -contains $normalized)

        foreach ($current in @($row)) {
            if ([string] $current.Type -ne 'Allow') { continue }
            if (-not (& $isOurs $current)) { continue }

            $rights = [string] $current.Rights

            if ($rights -match 'FullControl') {
                & $add $path 'Critical' (
                    ("'{0}' has FullControl on '{1}'. FullControl carries ChangePermissions and TakeOwnership, so the account in the boot image can rewrite the share's own security." -f $Identity, $path))
                continue
            }

            if (($rights -match 'Write|Modify|Delete|ChangePermissions|TakeOwnership') -and (-not $mayWrite)) {
                & $add $path 'Warning' (
                    ("'{0}' can write to '{1}' ({2}). The deployment account is expected to be read-only everywhere except {3}." -f
                        $Identity, $path, $rights, ($writableFolder -join '\ and ')))
            }
        }
    }

    # -- the result -----------------------------------------------------------

    $order = @{ 'Critical' = 0; 'Warning' = 1; 'Information' = 2 }
    $sorted = @($finding | Sort-Object -Property @{ Expression = { $order[[string] $_.Severity] } }, 'Path')

    $blocking = @($sorted | Where-Object { [string] $_.Severity -ne 'Information' })

    return [pscustomobject] @{
        Compliant = [bool] ($blocking.Count -eq 0)
        Finding   = [object[]] $sorted
    }
}