1 #!powershell
2 
3 # Copyright: (c) 2017, Noah Sparks <nsparks@outlook.com>
4 # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
5 
6 #Requires -Module Ansible.ModuleUtils.Legacy
7 #Requires -Module Ansible.ModuleUtils.SID
8 
9 $params = Parse-Args -arguments $args -supports_check_mode $true
10 $check_mode = Get-AnsibleParam -obj $params -name "_ansible_check_mode" -type "bool" -default $false
11 
12 # module parameters
13 $path = Get-AnsibleParam -obj $params -name "path" -type "path" -failifempty $true -aliases "destination","dest"
14 $user = Get-AnsibleParam -obj $params -name "user" -type "str" -failifempty $true
15 $rights = Get-AnsibleParam -obj $params -name "rights" -type "list"
16 $inheritance_flags = Get-AnsibleParam -obj $params -name "inheritance_flags" -type "list" -default 'ContainerInherit','ObjectInherit'
17 $propagation_flags = Get-AnsibleParam -obj $params -name "propagation_flags" -type "str" -default "none" -ValidateSet 'InheritOnly','None','NoPropagateInherit'
18 $audit_flags = Get-AnsibleParam -obj $params -name "audit_flags" -type "list" -default 'success'
19 $state = Get-AnsibleParam -obj $params -name "state" -type "str" -default "present" -validateset 'present','absent'
20 
21 #Make sure target path is valid
22 If (-not (Test-Path -Path $path) )
23 {
24     Fail-Json -obj $result -message "defined path ($path) is not found/invalid"
25 }
26 
27 #function get current audit rules and convert to hashtable
Get-CurrentAuditRules($path)28 Function Get-CurrentAuditRules ($path) {
29     Try {
30         $ACL = Get-Acl $path -Audit
31     }
32     Catch {
33         Return "Unable to retrieve the ACL on $Path"
34     }
35 
36     $HT = Foreach ($Obj in $ACL.Audit)
37     {
38         @{
39             user = $Obj.IdentityReference.ToString()
40             rights = ($Obj | Select-Object -expand "*rights").ToString()
41             audit_flags = $Obj.AuditFlags.ToString()
42             is_inherited = $Obj.IsInherited.ToString()
43             inheritance_flags = $Obj.InheritanceFlags.ToString()
44             propagation_flags = $Obj.PropagationFlags.ToString()
45         }
46     }
47 
48     If (-Not $HT)
49     {
50         "No audit rules defined on $path"
51     }
52     Else {$HT}
53 }
54 
55 $result = @{
56     changed = $false
57     current_audit_rules = Get-CurrentAuditRules $path
58 }
59 
60 #Make sure identity is valid and can be looked up
61 Try {
62     $SID = Convert-ToSid $user
63 }
64 Catch {
65     Fail-Json -obj $result -message "Failed to lookup the identity ($user) - $($_.exception.message)"
66 }
67 
68 #get the path type
69 $ItemType = (Get-Item $path).GetType()
70 switch ($ItemType)
71 {
72     ([Microsoft.Win32.RegistryKey]) {$registry = $true;  $result.path_type = 'registry'}
73     ([System.IO.FileInfo]) {$file = $true;  $result.path_type = 'file'}
74     ([System.IO.DirectoryInfo]) {$result.path_type = 'directory'}
75 }
76 
77 #Get current acl/audit rules on the target
78 Try {
79     $ACL = Get-Acl $path -Audit
80 }
81 Catch {
82     Fail-Json -obj $result -message "Unable to retrieve the ACL on $Path -  $($_.Exception.Message)"
83 }
84 
85 #configure acl object to remove the specified user
86 If ($state -eq 'absent')
87 {
88     #Try and find an identity on the object that matches user
89     #We skip inherited items since we can't remove those
90     $ToRemove = ($ACL.Audit | Where-Object {$_.IdentityReference.Translate([System.Security.Principal.SecurityIdentifier]) -eq $SID -and
91     $_.IsInherited -eq $false}).IdentityReference
92 
93     #Exit with changed false if no identity is found
94     If (-Not $ToRemove)
95     {
96         $result.current_audit_rules = Get-CurrentAuditRules $path
97         Exit-Json -obj $result
98     }
99 
100     #update the ACL object if identity found
101     Try
102     {
103         $ToRemove | ForEach-Object { $ACL.PurgeAuditRules($_) }
104     }
105     Catch
106     {
107         $result.current_audit_rules = Get-CurrentAuditRules $path
108         Fail-Json -obj $result -message "Failed to remove audit rule: $($_.Exception.Message)"
109     }
110 }
111 
112 Else
113 {
114     If ($registry)
115     {
116         $PossibleRights = [System.Enum]::GetNames([System.Security.AccessControl.RegistryRights])
117 
118         Foreach ($right in $rights)
119         {
120             if ($right -notin $PossibleRights)
121             {
122                 Fail-Json -obj $result -message "$right does not seem to be a valid REGISTRY right"
123             }
124         }
125 
126         $NewAccessRule = New-Object System.Security.AccessControl.RegistryAuditRule($user,$rights,$inheritance_flags,$propagation_flags,$audit_flags)
127     }
128     Else
129     {
130         $PossibleRights = [System.Enum]::GetNames([System.Security.AccessControl.FileSystemRights])
131 
132         Foreach ($right in $rights)
133         {
134             if ($right -notin $PossibleRights)
135             {
136                 Fail-Json -obj $result -message "$right does not seem to be a valid FILE SYSTEM right"
137             }
138         }
139 
140         If ($file -and $inheritance_flags -ne 'none')
141         {
142             Fail-Json -obj $result -message "The target type is a file. inheritance_flags must be changed to 'none'"
143         }
144 
145         $NewAccessRule = New-Object System.Security.AccessControl.FileSystemAuditRule($user,$rights,$inheritance_flags,$propagation_flags,$audit_flags)
146     }
147 
148     #exit here if any existing rule matches defined rule since no change is needed
149     #if we need to ignore inherited rules in the future, this would be where to do it
150     #Just filter out inherited rules from $ACL.Audit
151     Foreach ($group in $ACL.Audit | Where-Object {$_.IsInherited -eq $false})
152     {
153         If (
154             ($group | Select-Object -expand "*Rights") -eq ($NewAccessRule | Select-Object -expand "*Rights") -and
155             $group.AuditFlags -eq $NewAccessRule.AuditFlags -and
156             $group.IdentityReference.Translate([System.Security.Principal.SecurityIdentifier]) -eq $SID -and
157             $group.InheritanceFlags -eq $NewAccessRule.InheritanceFlags -and
158             $group.PropagationFlags -eq $NewAccessRule.PropagationFlags
159         )
160         {
161             $result.current_audit_rules = Get-CurrentAuditRules $path
162             Exit-Json -obj $result
163         }
164     }
165 
166     #try and set the acl object. AddAuditRule allows for multiple entries to exist under the same
167     #identity...so if someone wanted success: write and failure: delete for example, that setup would be
168     #possible. The alternative is SetAuditRule which would instead modify an existing rule and not allow
169     #for setting the above example.
170     Try
171     {
172         $ACL.AddAuditRule($NewAccessRule)
173     }
174     Catch
175     {
176         Fail-Json -obj $result -message "Failed to set the audit rule: $($_.Exception.Message)"
177     }
178 }
179 
180 
181 #finally set the permissions
182 Try {
183     Set-Acl -Path $path -ACLObject $ACL -WhatIf:$check_mode
184 }
185 Catch {
186     $result.current_audit_rules = Get-CurrentAuditRules $path
187     Fail-Json -obj $result -message "Failed to apply audit change: $($_.Exception.Message)"
188 }
189 
190 #exit here after a change is applied
191 $result.current_audit_rules = Get-CurrentAuditRules $path
192 $result.changed = $true
193 Exit-Json -obj $result
194