StrictAclTools.psm1

<#
.SYNOPSIS
Utilities for inspecting and enforcing strict Windows ACLs.

.DESCRIPTION
Provides commands to inspect filesystem ACLs and to enforce strict, single-user
ownership and access on files and directory trees. This is particularly useful
for Windows OpenSSH scenarios where private keys and config fragments must not
be accessible by other users or inherited from parent folders.

.NOTES
Author: DJ Stomp <85457381+DJStompZone@users.noreply.github.com>
GitHub: https://github.com/DJStompZone/StrictAclTools
License: MIT
#>


function Get-CustomAclForFile {
    <#
    .SYNOPSIS
    Returns a simplified view of a file or directory ACL.

    .DESCRIPTION
    Retrieves the owner and access rules for the specified filesystem item and
    returns them in a simplified custom object for easier inspection or export.

    .PARAMETER File
    File or directory to inspect.

    .EXAMPLE
    Get-Item "$HOME\.ssh\id_rsa" | Get-CustomAclForFile

    .NOTES
    Author: DJ Stomp <85457381+DJStompZone@users.noreply.github.com>
    GitHub: https://github.com/DJStompZone/StrictAclTools
    License: MIT
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
        [System.IO.FileSystemInfo]$File
    )

    process {
        $acl = Get-Acl -LiteralPath $File.FullName

        [PSCustomObject]@{
            FilePath = $File.FullName
            Owner = $acl.Owner
            Access = $acl.Access | Select-Object IdentityReference, FileSystemRights, AccessControlType, IsInherited, InheritanceFlags, PropagationFlags
        }
    }
}

function Set-StrictFileAcl {
    <#
    .SYNOPSIS
    Replaces all ACL entries on a file or directory with a single allow rule for one user.

    .DESCRIPTION
    Disables inheritance, sets the owner, removes all existing access rules,
    adds a single FullControl allow rule for the specified account, and writes
    the ACL back to disk.

    .PARAMETER Path
    File or directory path to update.

    .PARAMETER Username
    Account that should own and exclusively control the target.

    .EXAMPLE
    Set-StrictFileAcl -Path "$HOME\.ssh\csb\config" -Username 'DESKTOP\MyUsername'

    .EXAMPLE
    Set-StrictFileAcl -Path "$HOME\.ssh\id_rsa" -Username [System.Security.Principal.NTAccount]"$env:USERDOMAIN\$env:USERNAME"

    .EXAMPLE
    Get-ChildItem "$HOME\.ssh" -File | Set-StrictFileAcl -Username 'DESKTOP\MyUsername' -WhatIf

    .NOTES
    Author: DJ Stomp <85457381+DJStompZone@users.noreply.github.com>
    GitHub: https://github.com/DJStompZone
    License: MIT
    #>

    [CmdletBinding(SupportsShouldProcess = $true)]
    param(
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipelineByPropertyName = $true)]
        [Alias('FullName')]
        [ValidateNotNullOrEmpty()]
        [string]$Path,

        [Parameter(Mandatory = $true, Position = 1)]
        [System.Security.Principal.NTAccount]$Username
    )

    process {
        $resolvedPath = (Resolve-Path -LiteralPath $Path).Path

        if ($PSCmdlet.ShouldProcess($resolvedPath, "Set strict ACL for $Username")) {
            $acl = Get-Acl -LiteralPath $resolvedPath
            $acl.SetAccessRuleProtection($true, $false)
            $acl.SetOwner($Username)

            foreach ($rule in @($acl.Access)) {
                $null = $acl.RemoveAccessRule($rule)
            }

            $newRule = [System.Security.AccessControl.FileSystemAccessRule]::new(
                $Username,
                [System.Security.AccessControl.FileSystemRights]::FullControl,
                [System.Security.AccessControl.AccessControlType]::Allow
            )

            $acl.AddAccessRule($newRule)
            Set-Acl -LiteralPath $resolvedPath -AclObject $acl
        }
    }
}

function Set-StrictAclTree {
    <#
    .SYNOPSIS
    Applies a strict single-user ACL to a directory and everything under it.

    .DESCRIPTION
    Updates the root directory first, then recursively updates all child files
    and directories so the specified account is the sole owner and access holder.

    .PARAMETER RootPath
    Root directory to process.

    .PARAMETER Username
    Account that should own and exclusively control the tree.

    .EXAMPLE
    Set-StrictAclTree -RootPath "$HOME\.ssh" -Username 'DESKTOP\MyUsername'

    .EXAMPLE
    Set-StrictAclTree -RootPath "$HOME\.ssh" -Username [System.Security.Principal.NTAccount]"$env:USERDOMAIN\$env:USERNAME" -WhatIf

    .NOTES
    Author: DJ Stomp <85457381+DJStompZone@users.noreply.github.com>
    GitHub: https://github.com/DJStompZone/StrictAclTools
    License: MIT
    #>

    [CmdletBinding(SupportsShouldProcess = $true)]
    param(
        [Parameter(Mandatory = $true, Position = 0)]
        [ValidateNotNullOrEmpty()]
        [string]$RootPath,

        [Parameter(Mandatory = $true, Position = 1)]
        [System.Security.Principal.NTAccount]$Username
    )

    $resolvedRoot = (Resolve-Path -LiteralPath $RootPath).Path

    Set-StrictFileAcl -Path $resolvedRoot -Username $Username

    Get-ChildItem -LiteralPath $resolvedRoot -Force -Recurse |
        ForEach-Object {
            Set-StrictFileAcl -Path $_.FullName -Username $Username
        }
}

Export-ModuleMember -Function Get-CustomAclForFile, Set-StrictFileAcl, Set-StrictAclTree