You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

232 lines
12 KiB

<#
Tests the error handling that the cmdlets with -Path share, with the module built in NTFSSecurity\bin\Release on
files in a sandbox folder: a path that doesn't exist, an item whose owner may not read its permissions, and an item
whose owner may not change its permissions, which the cmdlets that write the DACL handle by taking ownership. Each
error belongs to its path only, and the cmdlet continues with the next one. The tests turn the module setting
EnablePrivileges off, so that an elevated session meets the same denials as a basic user, and restore it.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
'PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Pester shares variables between blocks.'
)]
param ()
BeforeDiscovery {
Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath 'TestHelpers.psm1') -Force
$holdsSecurityPrivilege = Test-PrivilegeHeld -Name 'SeSecurityPrivilege'
$currentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent().User.Value
$readEntry = @{ Account = 'S-1-1-0'; AccessRights = 'ReadData' }
# An audit entry on a file has no inheritance flags.
$auditEntry = @{ Account = 'S-1-1-0'; AccessRights = 'ReadData'; InheritanceFlags = 'None'; PropagationFlags = 'None' }
# Output: whether the cmdlet writes an object for the next path, an existing file.
$missingPathCases = @(
@{ Command = 'Get-NTFSAccess'; Parameters = @{}; ErrorId = 'ReadFileError'; Output = $true }
@{ Command = 'Add-NTFSAccess'; Parameters = $readEntry; ErrorId = 'ReadFileError'; Output = $false }
@{ Command = 'Remove-NTFSAccess'; Parameters = $readEntry; ErrorId = 'ReadFileError'; Output = $false }
@{ Command = 'Clear-NTFSAccess'; Parameters = @{}; ErrorId = 'ReadFileError'; Output = $false }
@{ Command = 'Get-NTFSEffectiveAccess'; Parameters = @{ Account = 'S-1-1-0' }; ErrorId = 'ReadFileError'; Output = $true }
@{ Command = 'Get-NTFSOrphanedAccess'; Parameters = @{}; ErrorId = 'ReadFileError'; Output = $false }
@{ Command = 'Get-NTFSInheritance'; Parameters = @{}; ErrorId = 'ReadFileError'; Output = $true }
@{ Command = 'Enable-NTFSAccessInheritance'; Parameters = @{}; ErrorId = 'ReadFileError'; Output = $false }
@{ Command = 'Disable-NTFSAccessInheritance'; Parameters = @{}; ErrorId = 'ReadFileError'; Output = $false }
@{ Command = 'Set-NTFSInheritance'; Parameters = @{ AccessInheritanceEnabled = $true }; ErrorId = 'ReadFileError'; Output = $false }
@{ Command = 'Get-NTFSOwner'; Parameters = @{}; ErrorId = 'ReadFileError'; Output = $true }
@{ Command = 'Set-NTFSOwner'; Parameters = @{ Account = $currentUser }; ErrorId = 'ReadFileError'; Output = $false }
@{ Command = 'Get-NTFSSecurityDescriptor'; Parameters = @{}; ErrorId = 'ReadFileError'; Output = $true }
@{ Command = 'Get-Item2'; Parameters = @{}; ErrorId = 'FileNotFound'; Output = $true }
@{ Command = 'Get-FileHash2'; Parameters = @{}; ErrorId = 'ReadFileError'; Output = $true }
@{ Command = 'Get-NTFSHardLink'; Parameters = @{}; ErrorId = 'FileNotFound'; Output = $true }
)
# The audit cmdlets need the Security privilege for the next path.
$missingPathAuditCases = @(
@{ Command = 'Get-NTFSAudit'; Parameters = @{} }
@{ Command = 'Add-NTFSAudit'; Parameters = $auditEntry }
@{ Command = 'Remove-NTFSAudit'; Parameters = $auditEntry }
@{ Command = 'Clear-NTFSAudit'; Parameters = @{} }
@{ Command = 'Enable-NTFSAuditInheritance'; Parameters = @{} }
@{ Command = 'Disable-NTFSAuditInheritance'; Parameters = @{} }
)
$deniedReadCases = @(
@{ Command = 'Get-NTFSAccess'; Parameters = @{}; Output = $true }
@{ Command = 'Get-NTFSEffectiveAccess'; Parameters = @{ Account = 'S-1-1-0' }; Output = $true }
@{ Command = 'Get-NTFSOrphanedAccess'; Parameters = @{}; Output = $false }
@{ Command = 'Get-NTFSInheritance'; Parameters = @{}; Output = $true }
@{ Command = 'Get-NTFSOwner'; Parameters = @{}; Output = $true }
@{ Command = 'Get-NTFSSecurityDescriptor'; Parameters = @{}; Output = $true }
)
}
BeforeAll {
Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath 'TestHelpers.psm1') -Force
$modulePath = Join-Path -Path $PSScriptRoot -ChildPath '..\NTFSSecurity\bin\Release\NTFSSecurity.psd1'
Import-Module -Name $modulePath -Force -ErrorAction Stop
$sandbox = New-TestSandbox -Name 'PathErrors'
Push-Location -LiteralPath $sandbox
$privateData = (Get-Module -Name NTFSSecurity).PrivateData
$enablePrivileges = $privateData['EnablePrivileges']
$privateData['EnablePrivileges'] = $false
$sidType = [System.Security.Principal.SecurityIdentifier]
function Get-TestMissingPath {
$path = Join-Path -Path $sandbox -ChildPath ('Missing-{0}' -f [guid]::NewGuid().ToString('N').Substring(0, 8))
Assert-TestSandboxPath -Sandbox $sandbox -Path $path
$path
}
function Get-TestAcl {
# .NET, because Get-Acl of an elevated Windows PowerShell reads also items that deny reading their permissions.
# .NET Core has the method as an extension method.
param ([string] $Path)
$info = New-Object -TypeName 'System.IO.FileInfo' -ArgumentList $Path
if ($PSVersionTable.PSEdition -eq 'Desktop') {
$info.GetAccessControl()
}
else {
[System.IO.FileSystemAclExtensions]::GetAccessControl($info)
}
}
}
AfterAll {
$privateData['EnablePrivileges'] = $enablePrivileges
Pop-Location
Remove-TestSandbox -Sandbox $sandbox
Remove-Module -Name NTFSSecurity -Force -ErrorAction SilentlyContinue
}
Describe 'A path that does not exist' {
It '<Command> should write a <ErrorId> for it and continue with the next path' -ForEach $missingPathCases {
$missing = Get-TestMissingPath
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'Next'
$output = @(& $Command -Path $missing, $file @Parameters -ErrorVariable pathErrors -ErrorAction SilentlyContinue -WarningAction SilentlyContinue)
$pathErrors | Should -HaveCount 1
$pathErrors[0].FullyQualifiedErrorId | Should -BeLike "$ErrorId,*"
$pathErrors[0].TargetObject | Should -Be $missing
if ($Output) {
$output | Should -Not -BeNullOrEmpty
}
}
It '<Command> should write a ReadFileError for it and continue with the next path' -ForEach $missingPathAuditCases -Skip:(-not $holdsSecurityPrivilege) {
$privateData['EnablePrivileges'] = $true
try {
$missing = Get-TestMissingPath
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'NextAudit'
& $Command -Path $missing, $file @Parameters -ErrorVariable pathErrors -ErrorAction SilentlyContinue | Out-Null
}
finally {
$privateData['EnablePrivileges'] = $false
}
$pathErrors | Should -HaveCount 1
$pathErrors[0].FullyQualifiedErrorId | Should -BeLike 'ReadFileError,*'
$pathErrors[0].TargetObject | Should -Be $missing
}
}
Describe 'An item whose owner may not read its permissions' {
# A deny entry for OWNER RIGHTS replaces the right of the owner to read the security descriptor. Taking ownership
# can't help: the cmdlet must read the owner first, which needs the same right. Before 5.0.0-rc6,
# Get-NTFSOrphanedAccess reported this as an AddAceError.
It '<Command> should write a ReadSecurityError for it and continue with the next path' -ForEach $deniedReadCases {
$blocked = New-TestSandboxItem -Sandbox $sandbox -Name 'Unreadable'
Block-TestReadPermission -Sandbox $sandbox -Path $blocked
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'NextReadable'
$output = @(& $Command -Path $blocked, $file @Parameters -ErrorVariable pathErrors -ErrorAction SilentlyContinue -WarningAction SilentlyContinue)
$pathErrors | Should -HaveCount 1
$pathErrors[0].FullyQualifiedErrorId | Should -BeLike 'ReadSecurityError,*'
$pathErrors[0].TargetObject | Should -Be $blocked
if ($Output) {
$output | Should -Not -BeNullOrEmpty
}
}
}
Describe 'An item whose owner may not change its permissions' {
# A deny entry for OWNER RIGHTS replaces the right of the owner to change the DACL. The cmdlets take ownership,
# which Windows answers by removing the OWNER RIGHTS entries, write the DACL, and set the previous owner back.
BeforeEach {
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'Unchangeable'
$acl = Get-TestAcl -Path $file
$owner = $acl.GetOwner($sidType).Value
}
It 'Add-NTFSAccess should take ownership, add the entry, and set the owner back' {
Add-TestDenyRule -Sandbox $sandbox -Path $file -Rights @{ 'S-1-3-4' = 'ChangePermissions' }
Add-NTFSAccess -Path $file -Account 'S-1-1-0' -AccessRights ReadData -ErrorVariable changeErrors -ErrorAction SilentlyContinue
$changeErrors | Should -BeNullOrEmpty
$acl = Get-TestAcl -Path $file
$acl.GetOwner($sidType).Value | Should -Be $owner
@($acl.GetAccessRules($true, $false, $sidType) | Where-Object -FilterScript { $_.IdentityReference.Value -eq 'S-1-1-0' }) | Should -HaveCount 1
@($acl.GetAccessRules($true, $false, $sidType) | Where-Object -FilterScript { $_.IdentityReference.Value -eq 'S-1-3-4' }) | Should -BeNullOrEmpty
}
It 'Remove-NTFSAccess should take ownership, remove the entry, and set the owner back' {
Add-NTFSAccess -Path $file -Account 'S-1-1-0' -AccessRights ReadData
Add-TestDenyRule -Sandbox $sandbox -Path $file -Rights @{ 'S-1-3-4' = 'ChangePermissions' }
Remove-NTFSAccess -Path $file -Account 'S-1-1-0' -AccessRights ReadData -ErrorVariable changeErrors -ErrorAction SilentlyContinue
$changeErrors | Should -BeNullOrEmpty
$acl = Get-TestAcl -Path $file
$acl.GetOwner($sidType).Value | Should -Be $owner
@($acl.GetAccessRules($true, $false, $sidType) | Where-Object -FilterScript { $_.IdentityReference.Value -eq 'S-1-1-0' }) | Should -BeNullOrEmpty
}
It 'Clear-NTFSAccess should take ownership, remove the explicit entries, and set the owner back' {
Add-NTFSAccess -Path $file -Account 'S-1-1-0' -AccessRights ReadData
Add-TestDenyRule -Sandbox $sandbox -Path $file -Rights @{ 'S-1-3-4' = 'ChangePermissions' }
Clear-NTFSAccess -Path $file -ErrorVariable changeErrors -ErrorAction SilentlyContinue
$changeErrors | Should -BeNullOrEmpty
$acl = Get-TestAcl -Path $file
$acl.GetOwner($sidType).Value | Should -Be $owner
@($acl.GetAccessRules($true, $false, $sidType)) | Should -BeNullOrEmpty
}
It 'Disable-NTFSAccessInheritance should take ownership, protect the DACL, and set the owner back' {
Add-TestDenyRule -Sandbox $sandbox -Path $file -Rights @{ 'S-1-3-4' = 'ChangePermissions' }
Disable-NTFSAccessInheritance -Path $file -ErrorVariable changeErrors -ErrorAction SilentlyContinue
$changeErrors | Should -BeNullOrEmpty
$acl = Get-TestAcl -Path $file
$acl.GetOwner($sidType).Value | Should -Be $owner
$acl.AreAccessRulesProtected | Should -BeTrue
}
It 'Enable-NTFSAccessInheritance should take ownership, let the DACL inherit, and set the owner back' {
Disable-NTFSAccessInheritance -Path $file
Add-TestDenyRule -Sandbox $sandbox -Path $file -Rights @{ 'S-1-3-4' = 'ChangePermissions' }
Enable-NTFSAccessInheritance -Path $file -ErrorVariable changeErrors -ErrorAction SilentlyContinue
$changeErrors | Should -BeNullOrEmpty
$acl = Get-TestAcl -Path $file
$acl.GetOwner($sidType).Value | Should -Be $owner
$acl.AreAccessRulesProtected | Should -BeFalse
}
It 'Set-NTFSInheritance should take ownership, protect the DACL, and set the owner back' {
Add-TestDenyRule -Sandbox $sandbox -Path $file -Rights @{ 'S-1-3-4' = 'ChangePermissions' }
Set-NTFSInheritance -Path $file -AccessInheritanceEnabled $false -ErrorVariable changeErrors -ErrorAction SilentlyContinue
$changeErrors | Should -BeNullOrEmpty
$acl = Get-TestAcl -Path $file
$acl.GetOwner($sidType).Value | Should -Be $owner
$acl.AreAccessRulesProtected | Should -BeTrue
}
}