mirror of https://github.com/raandree/NTFSSecurity
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.
685 lines
37 KiB
685 lines
37 KiB
<#
|
|
Tests the audit cmdlets of the module built in NTFSSecurity\bin\Release on files in a sandbox folder. Reading
|
|
and changing audit entries needs the Security privilege; tests that need it skip without it and run in CI,
|
|
whose runners are elevated.
|
|
#>
|
|
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
|
|
'PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Pester shares variables between blocks.'
|
|
)]
|
|
param ()
|
|
|
|
BeforeDiscovery {
|
|
Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath 'TestHelpers.psm1') -Force
|
|
$canReadAudit = Test-PrivilegeHeld -Name 'SeSecurityPrivilege'
|
|
# Assigning an owner other than the user or one of its groups needs the Restore privilege.
|
|
$canAssignAnyOwner = Test-PrivilegeHeld -Name 'SeRestorePrivilege'
|
|
}
|
|
|
|
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 'Audit'
|
|
Push-Location -LiteralPath $sandbox
|
|
|
|
$privateData = (Get-Module -Name NTFSSecurity).PrivateData
|
|
$enablePrivileges = $privateData['EnablePrivileges']
|
|
$sidType = [System.Security.Principal.SecurityIdentifier]
|
|
# An owner that the user can assign only with the Restore privilege
|
|
$trustedInstaller = 'S-1-5-80-956008885-3418522649-1831038044-1853292631-2271478464'
|
|
|
|
function Get-RestorePrivilegeState {
|
|
(Get-Privileges | Where-Object -Property Privilege -EQ -Value 'Restore').PrivilegeState
|
|
}
|
|
}
|
|
|
|
AfterAll {
|
|
$privateData['EnablePrivileges'] = $enablePrivileges
|
|
Pop-Location
|
|
Remove-TestSandbox -Sandbox $sandbox
|
|
Remove-Module -Name NTFSSecurity -Force -ErrorAction SilentlyContinue
|
|
}
|
|
|
|
Describe 'Get-NTFSAudit' {
|
|
Context 'When the audit entries cannot be read' {
|
|
It 'Should write an error without the Security privilege instead of returning nothing' -Skip:$canReadAudit {
|
|
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'NoPrivilege'
|
|
|
|
$entries = @(Get-NTFSAudit -Path $file -ErrorVariable auditErrors -ErrorAction SilentlyContinue)
|
|
|
|
$entries | Should -BeNullOrEmpty
|
|
$auditErrors | Should -HaveCount 1
|
|
$auditErrors[0].FullyQualifiedErrorId | Should -BeLike 'ReadSecurityError,*'
|
|
}
|
|
|
|
It 'Should write an error for a security descriptor that was read without the audit entries' {
|
|
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'AccessOnly'
|
|
$sd = New-Object -TypeName 'Security2.FileSystemSecurity2' -ArgumentList (
|
|
(Get-Item2 -Path $file), [System.Security.AccessControl.AccessControlSections]::Access
|
|
)
|
|
|
|
$entries = @(Get-NTFSAudit -SecurityDescriptor $sd -ErrorVariable auditErrors -ErrorAction SilentlyContinue)
|
|
|
|
$entries | Should -BeNullOrEmpty
|
|
$auditErrors | Should -HaveCount 1
|
|
$auditErrors[0].FullyQualifiedErrorId | Should -BeLike 'ReadSecurityError,*'
|
|
}
|
|
}
|
|
|
|
Context 'When a path fails after a path with audit entries' {
|
|
# Before 5.0.0, the cmdlet wrote the entries of the previous item again for the failing path. In CI, the deny
|
|
# entry made the original implementation, which also read the DACL, fail for the second path. The current one
|
|
# reads only the SACL, which the deny entry doesn't block; Access.Tests.ps1 guards the same loop fix in
|
|
# Get-NTFSAccess with a read that fails without elevation.
|
|
It 'Should return the entries of the first item once' -Skip:(-not $canReadAudit) {
|
|
$folder = New-TestSandboxItem -Sandbox $sandbox -Name 'Audited' -Directory
|
|
$denied = New-TestSandboxItem -Sandbox $sandbox -Name 'Denied'
|
|
Add-NTFSAudit -Path $folder -Account 'Everyone' -AccessRights Delete -AuditFlags Success
|
|
Block-TestReadPermission -Sandbox $sandbox -Path $denied
|
|
|
|
$entries = @(Get-NTFSAudit -Path $folder, $denied -ExcludeInherited -ErrorAction SilentlyContinue)
|
|
|
|
@($entries | Where-Object -Property FullName -EQ -Value $folder) | Should -HaveCount 1
|
|
}
|
|
}
|
|
}
|
|
|
|
Describe 'Add-NTFSAudit' {
|
|
Context 'Positional parameters' {
|
|
It 'Should take -Account at position 2 and -AccessRights at position 3 in the <_> parameter set' -ForEach @(
|
|
'PathSimple', 'PathComplex', 'SDSimple', 'SDComplex'
|
|
) {
|
|
$parameterSet = (Get-Command -Name Add-NTFSAudit).ParameterSets | Where-Object -Property Name -EQ -Value $_
|
|
$positions = @{}
|
|
$parameterSet.Parameters | Where-Object -Property Position -GE -Value 0 | ForEach-Object -Process {
|
|
$positions[$_.Name] = $_.Position
|
|
}
|
|
|
|
$positions['Account'] | Should -Be 2
|
|
$positions['AccessRights'] | Should -Be 3
|
|
}
|
|
|
|
# A descriptor from Get-NTFSSecurityDescriptor contains the audit entries only with the Security privilege.
|
|
It 'Should bind an account and access rights that are passed by position' -Skip:(-not $canReadAudit) {
|
|
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'Positional'
|
|
$sd = Get-NTFSSecurityDescriptor -Path $file
|
|
|
|
Add-NTFSAudit -SecurityDescriptor $sd 'Everyone' 'ReadData' -InheritanceFlags None -PropagationFlags None -ErrorAction Stop
|
|
|
|
$rules = $sd.SecurityDescriptor.GetAuditRules($true, $false, [System.Security.Principal.SecurityIdentifier])
|
|
@($rules | Where-Object -FilterScript { $_.IdentityReference.Value -eq 'S-1-1-0' }) | Should -HaveCount 1
|
|
}
|
|
}
|
|
|
|
# A descriptor from Get-NTFSSecurityDescriptor contains the audit entries only with the Security privilege.
|
|
Context 'With -PassThru' -Skip:(-not $canReadAudit) {
|
|
It 'Should return the audit entries of a security descriptor, not its access entries' {
|
|
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'PassThru'
|
|
$sd = Get-NTFSSecurityDescriptor -Path $file
|
|
|
|
$result = @(Add-NTFSAudit -SecurityDescriptor $sd -Account 'Everyone' -AccessRights ReadData -InheritanceFlags None -PropagationFlags None -PassThru)
|
|
|
|
$result | Should -Not -BeNullOrEmpty
|
|
$result | ForEach-Object -Process { $_ | Should -BeOfType [Security2.FileSystemAuditRule2] }
|
|
@($result | Where-Object -FilterScript { $_.Account.Sid -eq 'S-1-1-0' }) | Should -HaveCount 1
|
|
}
|
|
|
|
It 'Should report the inheritance of the audit entries in InheritanceEnabled, not that of the access entries' {
|
|
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'AuditProtected'
|
|
$sd = Get-NTFSSecurityDescriptor -Path $file
|
|
$sd.SecurityDescriptor.SetAuditRuleProtection($true, $false)
|
|
|
|
$result = @(Add-NTFSAudit -SecurityDescriptor $sd -Account 'Everyone' -AccessRights ReadData -InheritanceFlags None -PropagationFlags None -PassThru)
|
|
|
|
$sd.SecurityDescriptor.AreAccessRulesProtected | Should -BeFalse
|
|
$result | Should -Not -BeNullOrEmpty
|
|
$result | ForEach-Object -Process { $_.InheritanceEnabled | Should -BeFalse }
|
|
}
|
|
}
|
|
|
|
Context 'When the item has an owner that the user cannot assign' {
|
|
BeforeAll {
|
|
$privateData['EnablePrivileges'] = $false
|
|
}
|
|
|
|
AfterAll {
|
|
$privateData['EnablePrivileges'] = $enablePrivileges
|
|
}
|
|
|
|
# Before 5.0.0-rc3, the cmdlet wrote the unchanged owner back, which Windows refuses without the Restore
|
|
# privilege (#34).
|
|
It 'Should add the audit entry and keep the owner' -Skip:(-not ($canReadAudit -and $canAssignAnyOwner)) {
|
|
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'OtherOwner'
|
|
Set-TestOwner -Sandbox $sandbox -Path $file -Sid $trustedInstaller
|
|
Get-RestorePrivilegeState | Should -Be 'Disabled'
|
|
|
|
Add-NTFSAudit -Path $file -Account 'Everyone' -AccessRights ReadData -InheritanceFlags None -PropagationFlags None -ErrorVariable addErrors -ErrorAction SilentlyContinue
|
|
|
|
$addErrors | Should -BeNullOrEmpty
|
|
(Get-Acl -LiteralPath $file).GetOwner($sidType).Value | Should -Be $trustedInstaller
|
|
@(Get-NTFSAudit -Path $file -ExcludeInherited) | Should -HaveCount 1
|
|
}
|
|
}
|
|
|
|
Context 'With inherited access entries' {
|
|
# Before 5.0.0-rc3, the cmdlet also read and wrote the DACL. Read together with the SACL, the inherited entries
|
|
# of a DACL without the auto-inherit flag lose their inherited flag when the folder has no SACL, and the cmdlet
|
|
# wrote them back as explicit copies.
|
|
It 'Should leave the access entries unchanged' -Skip:(-not $canReadAudit) {
|
|
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'Inherited'
|
|
Assert-TestSandboxPath -Sandbox $sandbox -Path $file
|
|
$inheritedCount = @((Get-Acl -LiteralPath $file).GetAccessRules($false, $true, $sidType)).Count
|
|
$inheritedCount | Should -BeGreaterThan 0
|
|
|
|
Add-NTFSAudit -Path $file -Account 'Everyone' -AccessRights ReadData -InheritanceFlags None -PropagationFlags None
|
|
|
|
$acl = Get-Acl -LiteralPath $file
|
|
@($acl.GetAccessRules($true, $false, $sidType)) | Should -BeNullOrEmpty
|
|
@($acl.GetAccessRules($false, $true, $sidType)) | Should -HaveCount $inheritedCount
|
|
}
|
|
}
|
|
}
|
|
|
|
Describe 'Get-NTFSOrphanedAudit' {
|
|
BeforeAll {
|
|
$orphanedFile = New-TestSandboxItem -Sandbox $sandbox -Name 'OrphanedAudit'
|
|
$missing = Join-Path -Path $sandbox -ChildPath 'MissingOrphanedAudit.txt'
|
|
}
|
|
|
|
Context 'With the Security privilege' {
|
|
BeforeAll {
|
|
# Two entries of accounts that don't exist and one of Everyone, which resolves. Each test reads them, so
|
|
# none depends on another one.
|
|
foreach ($sid in 'S-1-5-21-1-2-3-1001', 'S-1-5-21-1-2-3-1002', 'S-1-1-0') {
|
|
Add-NTFSAudit -Path $orphanedFile -Account $sid -AccessRights ReadData -InheritanceFlags None -PropagationFlags None -ErrorAction Stop
|
|
}
|
|
|
|
$orphanedFolder = New-TestSandboxItem -Sandbox $sandbox -Name 'OrphanedAuditFolder' -Directory
|
|
Add-NTFSAudit -Path $orphanedFolder -Account 'S-1-5-21-1-2-3-1003' -AccessRights Delete -ErrorAction Stop
|
|
$inheritingFile = Join-Path -Path $orphanedFolder -ChildPath 'File.txt'
|
|
Assert-TestSandboxPath -Sandbox $sandbox -Path $inheritingFile
|
|
Set-Content -LiteralPath $inheritingFile -Value 'File'
|
|
}
|
|
|
|
# Before 5.0.0, the cmdlet wrote the entries of an item as one collection and ignored -Account.
|
|
It 'Should return one object per entry whose account cannot be resolved' -Skip:(-not $canReadAudit) {
|
|
$result = @(Get-NTFSOrphanedAudit -Path $orphanedFile)
|
|
|
|
$result | Should -HaveCount 2
|
|
$result | ForEach-Object -Process { $_ | Should -BeOfType [Security2.FileSystemAuditRule2] }
|
|
$result.Account.Sid | Should -Not -Contain 'S-1-1-0'
|
|
}
|
|
|
|
It 'Should return only the entries of -Account' -Skip:(-not $canReadAudit) {
|
|
$result = @(Get-NTFSOrphanedAudit -Path $orphanedFile -Account 'S-1-5-21-1-2-3-1002')
|
|
|
|
$result | Should -HaveCount 1
|
|
$result[0].Account.Sid | Should -Be 'S-1-5-21-1-2-3-1002'
|
|
}
|
|
|
|
It 'Should read the entries of a security descriptor' -Skip:(-not $canReadAudit) {
|
|
$result = @(Get-NTFSSecurityDescriptor -Path $orphanedFile | Get-NTFSOrphanedAudit -ErrorAction Stop)
|
|
|
|
$result | Should -HaveCount 2
|
|
$result | ForEach-Object -Process { $_.FullName | Should -Be $orphanedFile }
|
|
}
|
|
|
|
It 'Should return an inherited entry, and nothing with -ExcludeInherited' -Skip:(-not $canReadAudit) {
|
|
$result = @(Get-NTFSOrphanedAudit -Path $inheritingFile -ErrorAction Stop)
|
|
$explicitResult = @(Get-NTFSOrphanedAudit -Path $inheritingFile -ExcludeInherited -ErrorAction Stop)
|
|
|
|
$result | Should -HaveCount 1
|
|
$result[0].Account.Sid | Should -Be 'S-1-5-21-1-2-3-1003'
|
|
$result[0].IsInherited | Should -BeTrue
|
|
$explicitResult | Should -BeNullOrEmpty
|
|
}
|
|
|
|
It 'Should report the number of orphaned entries of each item in a verbose message' -Skip:(-not $canReadAudit) {
|
|
$messages = @(Get-NTFSOrphanedAudit -Path $orphanedFile -Verbose 4>&1 | Where-Object -FilterScript {
|
|
$_ -is [System.Management.Automation.VerboseRecord] })
|
|
|
|
$messages.Message | Should -Contain "Item $orphanedFile knows about 2 orphaned SIDs in its ACL"
|
|
}
|
|
}
|
|
|
|
It 'Should write an error for a path that does not exist and continue with the next path' -Skip:(-not $canReadAudit) {
|
|
$result = @(Get-NTFSOrphanedAudit -Path $missing, $orphanedFile -ErrorVariable orphanedErrors -ErrorAction SilentlyContinue)
|
|
|
|
$orphanedErrors | Should -HaveCount 1
|
|
$orphanedErrors[0].FullyQualifiedErrorId | Should -BeLike 'ReadError,*'
|
|
$orphanedErrors[0].TargetObject | Should -Be $missing
|
|
$result | ForEach-Object -Process { $_.FullName | Should -Be $orphanedFile }
|
|
}
|
|
|
|
# Since 5.0.0-rc7, the next path has an error of its own without the Security privilege, which shows that the cmdlet
|
|
# continued with it.
|
|
It 'Should write an error for a path that does not exist and continue with the next path without the Security privilege' -Skip:$canReadAudit {
|
|
$result = @(Get-NTFSOrphanedAudit -Path $missing, $orphanedFile -ErrorVariable orphanedErrors -ErrorAction SilentlyContinue)
|
|
|
|
$result | Should -BeNullOrEmpty
|
|
$orphanedErrors | Should -HaveCount 2
|
|
$orphanedErrors[0].FullyQualifiedErrorId | Should -BeLike 'ReadError,*'
|
|
$orphanedErrors[0].TargetObject | Should -Be $missing
|
|
$orphanedErrors[1].FullyQualifiedErrorId | Should -BeLike 'ReadSecurityError,*'
|
|
$orphanedErrors[1].TargetObject | Should -Be $orphanedFile
|
|
}
|
|
|
|
It 'Should write an error for a security descriptor that was read without the audit entries' {
|
|
$sd = New-Object -TypeName 'Security2.FileSystemSecurity2' -ArgumentList (
|
|
(Get-Item2 -Path $orphanedFile), [System.Security.AccessControl.AccessControlSections]::Access
|
|
)
|
|
|
|
$result = @($sd | Get-NTFSOrphanedAudit -ErrorVariable orphanedErrors -ErrorAction SilentlyContinue)
|
|
|
|
$result | Should -BeNullOrEmpty
|
|
$orphanedErrors | Should -HaveCount 1
|
|
$orphanedErrors[0].FullyQualifiedErrorId | Should -BeLike 'ReadSecurityError,*'
|
|
}
|
|
|
|
# Before 5.0.0-rc7, the cmdlet read the item without its audit entries and returned nothing, as for an item without
|
|
# orphaned entries; it now writes the error of Get-NTFSAudit.
|
|
It 'Should write a ReadSecurityError without the Security privilege instead of returning nothing' -Skip:$canReadAudit {
|
|
$result = @(Get-NTFSOrphanedAudit -Path $orphanedFile -ErrorVariable orphanedErrors -ErrorAction SilentlyContinue)
|
|
|
|
$result | Should -BeNullOrEmpty
|
|
$orphanedErrors | Should -HaveCount 1
|
|
$orphanedErrors[0].FullyQualifiedErrorId | Should -BeLike 'ReadSecurityError,*'
|
|
$orphanedErrors[0].TargetObject | Should -Be $orphanedFile
|
|
}
|
|
}
|
|
|
|
Describe 'Remove-NTFSAudit' {
|
|
Context 'When a path does not exist' {
|
|
BeforeAll {
|
|
$missing = Join-Path -Path $sandbox -ChildPath 'Missing.txt'
|
|
}
|
|
|
|
# Before 5.0.0, the cmdlet went on with the missing item and wrote a second, misleading RemoveAceError.
|
|
It 'Should write only the read error' {
|
|
Remove-NTFSAudit -Path $missing -Account 'Everyone' -AccessRights ReadData -ErrorVariable removeErrors -ErrorAction SilentlyContinue
|
|
|
|
$removeErrors | Should -HaveCount 1
|
|
$removeErrors[0].FullyQualifiedErrorId | Should -BeLike 'ReadFileError,*'
|
|
}
|
|
|
|
It 'Should not stop with -PassThru' {
|
|
{ Remove-NTFSAudit -Path $missing -Account 'Everyone' -AccessRights ReadData -PassThru -ErrorAction SilentlyContinue } |
|
|
Should -Not -Throw
|
|
}
|
|
}
|
|
|
|
# A descriptor from Get-NTFSSecurityDescriptor contains the audit entries only with the Security privilege.
|
|
Context 'With -RemoveSpecific' -Skip:(-not $canReadAudit) {
|
|
BeforeEach {
|
|
$removeFolder = New-TestSandboxItem -Sandbox $sandbox -Name 'RemoveSpecific' -Directory
|
|
$sd = Get-NTFSSecurityDescriptor -Path $removeFolder
|
|
Add-NTFSAudit -SecurityDescriptor $sd -Account 'Everyone' -AccessRights Modify
|
|
|
|
function Get-EveryoneAuditRule {
|
|
$sd.SecurityDescriptor.GetAuditRules($true, $false, [System.Security.Principal.SecurityIdentifier]) |
|
|
Where-Object -FilterScript { $_.IdentityReference.Value -eq 'S-1-1-0' }
|
|
}
|
|
}
|
|
|
|
It 'Should keep an audit entry that does not match exactly' {
|
|
Remove-NTFSAudit -SecurityDescriptor $sd -Account 'Everyone' -AccessRights ReadData -RemoveSpecific
|
|
|
|
(Get-EveryoneAuditRule).FileSystemRights.HasFlag([System.Security.AccessControl.FileSystemRights]::Modify) | Should -BeTrue
|
|
}
|
|
|
|
It 'Should remove an audit entry that matches exactly' {
|
|
Remove-NTFSAudit -SecurityDescriptor $sd -Account 'Everyone' -AccessRights Modify -RemoveSpecific
|
|
|
|
Get-EveryoneAuditRule | Should -BeNullOrEmpty
|
|
}
|
|
|
|
It 'Should keep an audit entry that does not match exactly, given the path' {
|
|
Add-NTFSAudit -Path $removeFolder -Account 'Everyone' -AccessRights Modify
|
|
|
|
Remove-NTFSAudit -Path $removeFolder -Account 'Everyone' -AccessRights ReadData -RemoveSpecific -ErrorAction Stop
|
|
|
|
$entries = @(Get-NTFSAudit -Path $removeFolder -ExcludeInherited | Where-Object -FilterScript { $_.Account.Sid -eq 'S-1-1-0' })
|
|
$entries | Should -HaveCount 1
|
|
$entries[0].AccessRights.ToString() | Should -BeLike '*Modify*'
|
|
}
|
|
|
|
It 'Should remove an audit entry that matches exactly, given the path' {
|
|
Add-NTFSAudit -Path $removeFolder -Account 'Everyone' -AccessRights Modify
|
|
|
|
Remove-NTFSAudit -Path $removeFolder -Account 'Everyone' -AccessRights Modify -RemoveSpecific -ErrorAction Stop
|
|
|
|
Get-NTFSAudit -Path $removeFolder -ExcludeInherited | Where-Object -FilterScript { $_.Account.Sid -eq 'S-1-1-0' } |
|
|
Should -BeNullOrEmpty
|
|
}
|
|
}
|
|
|
|
Context 'With -PassThru' {
|
|
It 'Should return the audit entries of the item, not its access entries' -Skip:(-not $canReadAudit) {
|
|
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'PassThru'
|
|
Add-NTFSAudit -Path $file -Account 'Everyone' -AccessRights ReadData -InheritanceFlags None -PropagationFlags None
|
|
Add-NTFSAudit -Path $file -Account 'BUILTIN\Users' -AccessRights Delete -InheritanceFlags None -PropagationFlags None
|
|
|
|
$result = @(Remove-NTFSAudit -Path $file -Account 'Everyone' -AccessRights ReadData -InheritanceFlags None -PropagationFlags None -PassThru)
|
|
|
|
$result | Should -Not -BeNullOrEmpty
|
|
$result | ForEach-Object -Process { $_ | Should -BeOfType [Security2.FileSystemAuditRule2] }
|
|
@($result | Where-Object -FilterScript { $_.Account.Sid -eq 'S-1-5-32-545' }) | Should -HaveCount 1
|
|
}
|
|
}
|
|
|
|
Context 'When the item has no SACL' {
|
|
# An item without audit entries can have no SACL at all, and Windows denies a write without any section:
|
|
# (5) Access is denied. Before 5.0.0-rc4, the cmdlet failed for such an item, although there was nothing to
|
|
# remove.
|
|
It 'Should write no error' -Skip:(-not $canReadAudit) {
|
|
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'NoSacl'
|
|
Assert-TestSandboxPath -Sandbox $sandbox -Path $file
|
|
$audit = New-Object -TypeName 'Security2.FileSystemSecurity2' -ArgumentList (
|
|
(Get-Item2 -Path $file), [System.Security.AccessControl.AccessControlSections]::Audit
|
|
)
|
|
$audit.SecurityDescriptor.GetSecurityDescriptorSddlForm('Audit') | Should -BeNullOrEmpty
|
|
|
|
Remove-NTFSAudit -Path $file -Account 'Everyone' -AccessRights ReadData -InheritanceFlags None -PropagationFlags None -ErrorVariable removeErrors -ErrorAction SilentlyContinue
|
|
|
|
$removeErrors | Should -BeNullOrEmpty
|
|
}
|
|
}
|
|
}
|
|
|
|
Describe 'Audit cmdlets with a security descriptor without the audit entries' {
|
|
# Before 5.0.0-rc4, only Get-NTFSAudit reported a security descriptor that was read without the audit entries, such
|
|
# as without the Security privilege. The other audit cmdlets changed the missing SACL in memory and wrote no error,
|
|
# and -PassThru returned nothing (#109).
|
|
It '<Command> should write an error and leave the descriptor without audit entries' -ForEach @(
|
|
@{ Command = 'Add-NTFSAudit'; Parameters = @{ Account = 'Everyone'; AccessRights = 'ReadData'; PassThru = $true } }
|
|
@{ Command = 'Remove-NTFSAudit'; Parameters = @{ Account = 'Everyone'; AccessRights = 'ReadData'; PassThru = $true } }
|
|
@{ Command = 'Clear-NTFSAudit'; Parameters = @{ DisableInheritance = $true } }
|
|
) {
|
|
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'AccessOnly'
|
|
$sd = New-Object -TypeName 'Security2.FileSystemSecurity2' -ArgumentList (
|
|
(Get-Item2 -Path $file), [System.Security.AccessControl.AccessControlSections]::Access
|
|
)
|
|
|
|
$result = @(& $Command -SecurityDescriptor $sd @Parameters -ErrorVariable auditErrors -ErrorAction SilentlyContinue)
|
|
|
|
$result | Should -BeNullOrEmpty
|
|
$auditErrors | Should -HaveCount 1
|
|
$auditErrors[0].FullyQualifiedErrorId | Should -BeLike 'ReadSecurityError,*'
|
|
# The descriptor was read with the access entries only, not without the Security privilege.
|
|
$auditErrors[0].Exception.Message | Should -Not -BeLike '*because it was read without the Security privilege*'
|
|
$sd.SecurityDescriptor.GetSecurityDescriptorSddlForm('Audit') | Should -BeNullOrEmpty
|
|
}
|
|
}
|
|
|
|
Describe 'Clear-NTFSAudit' {
|
|
Context 'When the item has an owner that the user cannot assign' {
|
|
BeforeAll {
|
|
$privateData['EnablePrivileges'] = $false
|
|
}
|
|
|
|
AfterAll {
|
|
$privateData['EnablePrivileges'] = $enablePrivileges
|
|
}
|
|
|
|
# Before 5.0.0-rc3, the cmdlet wrote the unchanged owner back, which Windows refuses without the Restore
|
|
# privilege (#34).
|
|
It 'Should remove the audit entries and keep the owner' -Skip:(-not ($canReadAudit -and $canAssignAnyOwner)) {
|
|
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'ClearOtherOwner'
|
|
Assert-TestSandboxPath -Sandbox $sandbox -Path $file
|
|
Add-NTFSAudit -Path $file -Account 'Everyone' -AccessRights ReadData -InheritanceFlags None -PropagationFlags None
|
|
Set-TestOwner -Sandbox $sandbox -Path $file -Sid $trustedInstaller
|
|
Get-RestorePrivilegeState | Should -Be 'Disabled'
|
|
|
|
Clear-NTFSAudit -Path $file -ErrorVariable clearErrors -ErrorAction SilentlyContinue
|
|
|
|
$clearErrors | Should -BeNullOrEmpty
|
|
(Get-Acl -LiteralPath $file).GetOwner($sidType).Value | Should -Be $trustedInstaller
|
|
@(Get-NTFSAudit -Path $file -ExcludeInherited) | Should -BeNullOrEmpty
|
|
}
|
|
}
|
|
|
|
Context 'When the item has no SACL' {
|
|
# An item without audit entries can have no SACL at all, and Windows denies a write without any section.
|
|
# Before 5.0.0-rc3, the cmdlet also read and wrote the DACL, and in an elevated session it wrote the inherited
|
|
# access entries back as explicit copies.
|
|
It 'Should write no error and leave the access entries unchanged' -Skip:(-not $canReadAudit) {
|
|
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'NoSacl'
|
|
Assert-TestSandboxPath -Sandbox $sandbox -Path $file
|
|
$audit = New-Object -TypeName 'Security2.FileSystemSecurity2' -ArgumentList (
|
|
(Get-Item2 -Path $file), [System.Security.AccessControl.AccessControlSections]::Audit
|
|
)
|
|
$audit.SecurityDescriptor.GetSecurityDescriptorSddlForm('Audit') | Should -BeNullOrEmpty
|
|
$inheritedCount = @((Get-Acl -LiteralPath $file).GetAccessRules($false, $true, $sidType)).Count
|
|
# Without inherited entries, the test couldn't see them copied as explicit ones (#110).
|
|
$inheritedCount | Should -BeGreaterThan 0
|
|
|
|
Clear-NTFSAudit -Path $file -ErrorVariable clearErrors -ErrorAction SilentlyContinue
|
|
|
|
$clearErrors | Should -BeNullOrEmpty
|
|
$acl = Get-Acl -LiteralPath $file
|
|
@($acl.GetAccessRules($true, $false, $sidType)) | Should -BeNullOrEmpty
|
|
@($acl.GetAccessRules($false, $true, $sidType)) | Should -HaveCount $inheritedCount
|
|
}
|
|
}
|
|
|
|
Context 'Without the Security privilege' {
|
|
# Before 5.0.0-rc3, the cmdlet read the security descriptor without its SACL, found no audit entries to remove,
|
|
# and finished without an error although nothing was changed; it also wrote the DACL back.
|
|
It 'Should write an error and leave the item unchanged' -Skip:$canReadAudit {
|
|
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'NoPrivilege'
|
|
Assert-TestSandboxPath -Sandbox $sandbox -Path $file
|
|
$sddl = (Get-Acl -LiteralPath $file).Sddl
|
|
|
|
Clear-NTFSAudit -Path $file -ErrorVariable clearErrors -ErrorAction SilentlyContinue
|
|
|
|
$clearErrors | Should -HaveCount 1
|
|
$clearErrors[0].FullyQualifiedErrorId | Should -BeLike 'ClearAclError,*'
|
|
(Get-Acl -LiteralPath $file).Sddl | Should -BeExactly $sddl
|
|
}
|
|
}
|
|
}
|
|
|
|
# Before 5.0.0-rc6, comparing an audit entry with anything threw an InvalidCastException.
|
|
Describe 'Comparing audit entries' {
|
|
It 'Should find an entry equal to itself and not to a string' -Skip:(-not $canReadAudit) {
|
|
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'Compare'
|
|
Add-NTFSAudit -Path $file -Account 'S-1-1-0' -AccessRights ReadData -InheritanceFlags None -PropagationFlags None
|
|
$entries = @(Get-NTFSAudit -Path $file -ExcludeInherited)
|
|
$entries | Should -HaveCount 1
|
|
|
|
$entries[0] -eq $entries[0] | Should -BeTrue
|
|
$entries -contains $entries[0] | Should -BeTrue
|
|
$entries[0].Equals('S-1-1-0') | Should -BeFalse
|
|
}
|
|
}
|
|
|
|
# Before 5.0.0-rc6, -ExcludeExplicit gave each inherited audit entry the source of another entry.
|
|
Describe 'InheritedFrom of audit entries' {
|
|
It 'Should name the folder that an inherited entry comes from, also with -ExcludeExplicit' -Skip:(-not $canReadAudit) {
|
|
$folder = New-TestSandboxItem -Sandbox $sandbox -Name 'InheritedFrom' -Directory
|
|
Add-NTFSAudit -Path $folder -Account 'S-1-1-0' -AccessRights ReadData -InheritanceFlags 'ContainerInherit, ObjectInherit' -PropagationFlags None
|
|
$file = Join-Path -Path $folder -ChildPath 'File.txt'
|
|
Assert-TestSandboxPath -Sandbox $sandbox -Path $file
|
|
Set-Content -LiteralPath $file -Value 'File'
|
|
Add-NTFSAudit -Path $file -Account 'S-1-5-32-546' -AccessRights Delete -InheritanceFlags None -PropagationFlags None
|
|
|
|
$all = @(Get-NTFSAudit -Path $file)
|
|
$inherited = @(Get-NTFSAudit -Path $file -ExcludeExplicit)
|
|
|
|
@($all | Where-Object -FilterScript { $_.IsInherited }) | Should -HaveCount 1
|
|
@($all | Where-Object -FilterScript { $_.IsInherited })[0].InheritedFrom | Should -Be $folder
|
|
$inherited | Should -HaveCount 1
|
|
$inherited[0].InheritedFrom | Should -Be $folder
|
|
}
|
|
|
|
# Windows names the folders of audit entries only for a caller whose Security privilege is enabled, and the cmdlets
|
|
# enable it. A caller of the library that doesn't gets the entries without sources. Before 5.0.0, the text lost its
|
|
# last character, and an explicit entry, which has no source, got it as well.
|
|
It 'Should name an unknown parent for an inherited entry and no source for an explicit entry when the privilege is disabled' -Skip:(-not $canReadAudit) {
|
|
$folder = New-TestSandboxItem -Sandbox $sandbox -Name 'InheritedFromDisabled' -Directory
|
|
Add-NTFSAudit -Path $folder -Account 'S-1-1-0' -AccessRights ReadData -InheritanceFlags 'ContainerInherit, ObjectInherit' -PropagationFlags None
|
|
$file = Join-Path -Path $folder -ChildPath 'File.txt'
|
|
Assert-TestSandboxPath -Sandbox $sandbox -Path $file
|
|
Set-Content -LiteralPath $file -Value 'File'
|
|
Add-NTFSAudit -Path $file -Account 'S-1-5-32-546' -AccessRights Delete -InheritanceFlags None -PropagationFlags None
|
|
$sd = Get-NTFSSecurityDescriptor -Path $file -ErrorAction Stop
|
|
Disable-Privileges -ErrorAction SilentlyContinue -WarningAction SilentlyContinue
|
|
|
|
$entries = @([Security2.FileSystemAuditRule2]::GetFileSystemAuditRules($sd, $true, $true, $true))
|
|
|
|
$inherited = @($entries | Where-Object -FilterScript { $_.IsInherited })
|
|
$inherited | Should -HaveCount 1
|
|
$inherited[0].InheritedFrom | Should -BeExactly 'unknown parent'
|
|
$explicit = @($entries | Where-Object -FilterScript { -not $_.IsInherited })
|
|
$explicit | Should -HaveCount 1
|
|
$explicit[0].InheritedFrom | Should -BeNullOrEmpty
|
|
}
|
|
|
|
# The module setting GetInheritedFrom turns off the lookup of the sources, which costs a call for each item.
|
|
It 'Should leave InheritedFrom empty when the module setting GetInheritedFrom is off' -Skip:(-not $canReadAudit) {
|
|
$folder = New-TestSandboxItem -Sandbox $sandbox -Name 'InheritedFromOff' -Directory
|
|
Add-NTFSAudit -Path $folder -Account 'S-1-1-0' -AccessRights ReadData -InheritanceFlags 'ContainerInherit, ObjectInherit' -PropagationFlags None
|
|
$file = Join-Path -Path $folder -ChildPath 'File.txt'
|
|
Assert-TestSandboxPath -Sandbox $sandbox -Path $file
|
|
Set-Content -LiteralPath $file -Value 'File'
|
|
$saved = $privateData['GetInheritedFrom']
|
|
$privateData['GetInheritedFrom'] = $false
|
|
try {
|
|
$result = @(Get-NTFSAudit -Path $file -ErrorAction Stop)
|
|
}
|
|
finally {
|
|
$privateData['GetInheritedFrom'] = $saved
|
|
}
|
|
|
|
$inherited = @($result | Where-Object -FilterScript { $_.IsInherited })
|
|
$inherited | Should -HaveCount 1
|
|
$inherited[0].InheritedFrom | Should -BeNullOrEmpty
|
|
}
|
|
}
|
|
|
|
Describe 'Audit changes with the Security privilege disabled' {
|
|
BeforeAll {
|
|
$holdsSecurityForOperations = Test-PrivilegeHeld -Name 'SeSecurityPrivilege'
|
|
}
|
|
|
|
BeforeEach {
|
|
$savedEnablePrivileges = $privateData['EnablePrivileges']
|
|
$securityWasEnabled = (Get-Privileges | Where-Object -Property Privilege -EQ -Value 'Security').PrivilegeState -eq 'Enabled'
|
|
}
|
|
|
|
AfterEach {
|
|
$privateData['EnablePrivileges'] = $savedEnablePrivileges
|
|
if ($securityWasEnabled) {
|
|
$null = [ProcessPrivileges.ProcessExtensions]::EnablePrivilege(
|
|
[Diagnostics.Process]::GetCurrentProcess(), [ProcessPrivileges.Privilege]::Security
|
|
)
|
|
}
|
|
else {
|
|
$null = [ProcessPrivileges.ProcessExtensions]::DisablePrivilege(
|
|
[Diagnostics.Process]::GetCurrentProcess(), [ProcessPrivileges.Privilege]::Security
|
|
)
|
|
}
|
|
}
|
|
|
|
It '<Command> should use a held privilege or report a missing one and continue to the next path' -ForEach @(
|
|
@{ Command = 'Add-NTFSAudit'; ErrorId = 'AddAceError'; Parameters = @{ Account = 'S-1-1-0'; AccessRights = 'ReadData'; PassThru = $true } }
|
|
@{ Command = 'Remove-NTFSAudit'; ErrorId = 'RemoveAceError'; Parameters = @{ Account = 'S-1-1-0'; AccessRights = 'Delete'; PassThru = $true } }
|
|
@{ Command = 'Clear-NTFSAudit'; ErrorId = 'ClearAclError'; Parameters = @{ DisableInheritance = $true } }
|
|
@{ Command = 'Enable-NTFSAuditInheritance'; ErrorId = 'ModifySdError'; Parameters = @{ PassThru = $true; RemoveExplicitAuditRules = $true } }
|
|
@{ Command = 'Disable-NTFSAuditInheritance'; ErrorId = 'ModifySdError'; Parameters = @{ PassThru = $true; RemoveInheritedAuditRules = $true } }
|
|
) {
|
|
$path = New-TestSandboxItem -Sandbox $sandbox -Name 'DisabledSecurity'
|
|
$missing = Join-Path -Path $sandbox -ChildPath ('MissingAudit-{0}' -f [guid]::NewGuid().ToString('N'))
|
|
Assert-TestSandboxPath -Sandbox $sandbox -Path $path, $missing
|
|
if ($holdsSecurityForOperations) {
|
|
$privateData['EnablePrivileges'] = $true
|
|
Add-NTFSAudit -Path $path -Account 'S-1-1-0' -AccessRights Delete -AuditFlags Success -AppliesTo ThisFolderOnly
|
|
$saclBefore = (Get-NTFSSecurityDescriptor -Path $path).SecurityDescriptor.GetSecurityDescriptorSddlForm('Audit')
|
|
}
|
|
$before = (Get-Acl -LiteralPath $path).Sddl
|
|
$privateData['EnablePrivileges'] = $false
|
|
$null = [ProcessPrivileges.ProcessExtensions]::DisablePrivilege(
|
|
[Diagnostics.Process]::GetCurrentProcess(), [ProcessPrivileges.Privilege]::Security
|
|
)
|
|
|
|
(Get-Privileges | Where-Object -Property Privilege -EQ -Value 'Security').PrivilegeState | Should -Not -Be 'Enabled'
|
|
|
|
$result = @(& $Command -Path $path, $missing @Parameters -ErrorVariable auditErrors -ErrorAction SilentlyContinue)
|
|
|
|
(Get-Acl -LiteralPath $path).Sddl | Should -BeExactly $before
|
|
if ($holdsSecurityForOperations) {
|
|
# AlphaFS temporarily enables a held Security privilege for SACL access, even with automatic privileges off.
|
|
(Get-Privileges | Where-Object -Property Privilege -EQ -Value 'Security').PrivilegeState | Should -Be 'Disabled'
|
|
$auditErrors | Should -HaveCount 1
|
|
$auditErrors[0].FullyQualifiedErrorId | Should -BeLike 'ReadFileError,*'
|
|
$auditErrors[0].CategoryInfo.Category | Should -Be 'OpenError'
|
|
$auditErrors[0].TargetObject | Should -BeExactly $missing
|
|
$written = Get-NTFSSecurityDescriptor -Path $path
|
|
$rules = @($written.SecurityDescriptor.GetAuditRules(
|
|
$true, $false, [System.Security.Principal.SecurityIdentifier]
|
|
))
|
|
switch ($Command) {
|
|
'Add-NTFSAudit' {
|
|
$result | Should -Not -BeNullOrEmpty
|
|
$result | ForEach-Object { $_.FullName | Should -BeExactly $path }
|
|
@($rules | Where-Object {
|
|
$_.IdentityReference.Value -eq 'S-1-1-0' -and $_.FileSystemRights.HasFlag(
|
|
[System.Security.AccessControl.FileSystemRights]::ReadData
|
|
)
|
|
}).Count | Should -BeGreaterThan 0
|
|
}
|
|
'Disable-NTFSAuditInheritance' {
|
|
$result | Should -HaveCount 1
|
|
$result[0].AuditInheritanceEnabled | Should -BeFalse
|
|
$rules | Should -HaveCount 1
|
|
$rules[0].FileSystemRights | Should -Be ([System.Security.AccessControl.FileSystemRights]::Delete)
|
|
}
|
|
'Enable-NTFSAuditInheritance' {
|
|
$result | Should -HaveCount 1
|
|
$result[0].AuditInheritanceEnabled | Should -BeTrue
|
|
$rules | Should -BeNullOrEmpty
|
|
}
|
|
default {
|
|
$result | Should -BeNullOrEmpty
|
|
$rules | Should -BeNullOrEmpty
|
|
}
|
|
}
|
|
$written.SecurityDescriptor.GetSecurityDescriptorSddlForm('Audit') | Should -Not -BeExactly $saclBefore
|
|
}
|
|
else {
|
|
$result | Should -BeNullOrEmpty
|
|
$auditErrors | Should -HaveCount 2
|
|
$auditErrors[0].FullyQualifiedErrorId | Should -BeLike "$ErrorId,*"
|
|
$auditErrors[0].CategoryInfo.Category | Should -Be 'WriteError'
|
|
$auditErrors[0].TargetObject | Should -BeExactly $path
|
|
$auditErrors[1].FullyQualifiedErrorId | Should -BeLike 'ReadFileError,*'
|
|
$auditErrors[1].CategoryInfo.Category | Should -Be 'OpenError'
|
|
$auditErrors[1].TargetObject | Should -BeExactly $missing
|
|
}
|
|
}
|
|
}
|
|
Describe 'Clear-NTFSAudit descriptor inheritance' {
|
|
It 'Should clear and protect the descriptor SACL without writing the <Type>' -Skip:(-not $canReadAudit) -ForEach @(
|
|
@{ Type = 'file' }
|
|
@{ Type = 'folder' }
|
|
) {
|
|
$path = New-TestSandboxItem -Sandbox $sandbox -Name 'ClearAuditDescriptor' -Directory:($Type -eq 'folder')
|
|
Assert-TestSandboxPath -Sandbox $sandbox -Path $path
|
|
Add-NTFSAudit -Path $path -Account 'S-1-1-0' -AccessRights Delete -AuditFlags Success -AppliesTo ThisFolderOnly
|
|
$before = Get-NTFSSecurityDescriptor -Path $path
|
|
$auditBefore = $before.SecurityDescriptor.GetSecurityDescriptorSddlForm('Audit')
|
|
$daclBefore = (Get-Acl -LiteralPath $path).Sddl
|
|
$sd = Get-NTFSSecurityDescriptor -Path $path
|
|
|
|
Clear-NTFSAudit -SecurityDescriptor $sd -DisableInheritance -ErrorAction Stop
|
|
|
|
$sd.SecurityDescriptor.AreAuditRulesProtected | Should -BeTrue
|
|
@($sd.SecurityDescriptor.GetAuditRules($true, $true, $sidType)) | Should -BeNullOrEmpty
|
|
(Get-NTFSSecurityDescriptor -Path $path).SecurityDescriptor.GetSecurityDescriptorSddlForm('Audit') |
|
|
Should -BeExactly $auditBefore
|
|
Set-NTFSSecurityDescriptor -SecurityDescriptor $sd -ErrorAction Stop
|
|
(Get-NTFSInheritance -Path $path).AuditInheritanceEnabled | Should -BeFalse
|
|
@(Get-NTFSAudit -Path $path) | Should -BeNullOrEmpty
|
|
(Get-Acl -LiteralPath $path).Sddl | Should -BeExactly $daclBefore
|
|
}
|
|
}
|
|
|