#!powershell # Copyright: (c) 2017, Ansible Project # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) #Requires -Module Ansible.ModuleUtils.Legacy #Requires -Module Ansible.ModuleUtils.SID $ErrorActionPreference = 'Stop' $params = Parse-Args $args -supports_check_mode $true $check_mode = Get-AnsibleParam -obj $params -name "_ansible_check_mode" -type "bool" -default $false $diff_mode = Get-AnsibleParam -obj $params -name "_ansible_diff" -type "bool" -default $false $_remote_tmp = Get-AnsibleParam $params "_ansible_remote_tmp" -type "path" -default $env:TMP $name = Get-AnsibleParam -obj $params -name "name" -type "str" -failifempty $true $users = Get-AnsibleParam -obj $params -name "users" -type "list" -failifempty $true $action = Get-AnsibleParam -obj $params -name "action" -type "str" -default "set" -validateset "add","remove","set" $result = @{ changed = $false added = @() removed = @() } if ($diff_mode) { $result.diff = @{} } $sec_helper_util = @" using System; using System.ComponentModel; using System.Runtime.InteropServices; using System.Security.Principal; namespace Ansible { public class LsaRightHelper : IDisposable { // Code modified from https://gallery.technet.microsoft.com/scriptcenter/Grant-Revoke-Query-user-26e259b0 enum Access : int { POLICY_READ = 0x20006, POLICY_ALL_ACCESS = 0x00F0FFF, POLICY_EXECUTE = 0X20801, POLICY_WRITE = 0X207F8 } IntPtr lsaHandle; const string LSA_DLL = "advapi32.dll"; const CharSet DEFAULT_CHAR_SET = CharSet.Unicode; const uint STATUS_NO_MORE_ENTRIES = 0x8000001a; const uint STATUS_NO_SUCH_PRIVILEGE = 0xc0000060; internal sealed class Sid : IDisposable { public IntPtr pSid = IntPtr.Zero; public SecurityIdentifier sid = null; public Sid(string sidString) { try { sid = new SecurityIdentifier(sidString); } catch { throw new ArgumentException(String.Format("SID string {0} could not be converted to SecurityIdentifier", sidString)); } Byte[] buffer = new Byte[sid.BinaryLength]; sid.GetBinaryForm(buffer, 0); pSid = Marshal.AllocHGlobal(sid.BinaryLength); Marshal.Copy(buffer, 0, pSid, sid.BinaryLength); } public void Dispose() { if (pSid != IntPtr.Zero) { Marshal.FreeHGlobal(pSid); pSid = IntPtr.Zero; } GC.SuppressFinalize(this); } ~Sid() { Dispose(); } } [StructLayout(LayoutKind.Sequential)] private struct LSA_OBJECT_ATTRIBUTES { public int Length; public IntPtr RootDirectory; public IntPtr ObjectName; public int Attributes; public IntPtr SecurityDescriptor; public IntPtr SecurityQualityOfService; } [StructLayout(LayoutKind.Sequential, CharSet = DEFAULT_CHAR_SET)] private struct LSA_UNICODE_STRING { public ushort Length; public ushort MaximumLength; [MarshalAs(UnmanagedType.LPWStr)] public string Buffer; } [StructLayout(LayoutKind.Sequential)] private struct LSA_ENUMERATION_INFORMATION { public IntPtr Sid; } [DllImport(LSA_DLL, CharSet = DEFAULT_CHAR_SET, SetLastError = true)] private static extern uint LsaOpenPolicy( LSA_UNICODE_STRING[] SystemName, ref LSA_OBJECT_ATTRIBUTES ObjectAttributes, int AccessMask, out IntPtr PolicyHandle ); [DllImport(LSA_DLL, CharSet = DEFAULT_CHAR_SET, SetLastError = true)] private static extern uint LsaAddAccountRights( IntPtr PolicyHandle, IntPtr pSID, LSA_UNICODE_STRING[] UserRights, int CountOfRights ); [DllImport(LSA_DLL, CharSet = DEFAULT_CHAR_SET, SetLastError = true)] private static extern uint LsaRemoveAccountRights( IntPtr PolicyHandle, IntPtr pSID, bool AllRights, LSA_UNICODE_STRING[] UserRights, int CountOfRights ); [DllImport(LSA_DLL, CharSet = DEFAULT_CHAR_SET, SetLastError = true)] private static extern uint LsaEnumerateAccountsWithUserRight( IntPtr PolicyHandle, LSA_UNICODE_STRING[] UserRights, out IntPtr EnumerationBuffer, out ulong CountReturned ); [DllImport(LSA_DLL)] private static extern int LsaNtStatusToWinError(int NTSTATUS); [DllImport(LSA_DLL)] private static extern int LsaClose(IntPtr PolicyHandle); [DllImport(LSA_DLL)] private static extern int LsaFreeMemory(IntPtr Buffer); public LsaRightHelper() { LSA_OBJECT_ATTRIBUTES lsaAttr; lsaAttr.RootDirectory = IntPtr.Zero; lsaAttr.ObjectName = IntPtr.Zero; lsaAttr.Attributes = 0; lsaAttr.SecurityDescriptor = IntPtr.Zero; lsaAttr.SecurityQualityOfService = IntPtr.Zero; lsaAttr.Length = Marshal.SizeOf(typeof(LSA_OBJECT_ATTRIBUTES)); lsaHandle = IntPtr.Zero; LSA_UNICODE_STRING[] system = new LSA_UNICODE_STRING[1]; system[0] = InitLsaString(""); uint ret = LsaOpenPolicy(system, ref lsaAttr, (int)Access.POLICY_ALL_ACCESS, out lsaHandle); if (ret != 0) throw new Win32Exception(LsaNtStatusToWinError((int)ret)); } public void AddPrivilege(string sidString, string privilege) { uint ret = 0; using (Sid sid = new Sid(sidString)) { LSA_UNICODE_STRING[] privileges = new LSA_UNICODE_STRING[1]; privileges[0] = InitLsaString(privilege); ret = LsaAddAccountRights(lsaHandle, sid.pSid, privileges, 1); } if (ret != 0) throw new Win32Exception(LsaNtStatusToWinError((int)ret)); } public void RemovePrivilege(string sidString, string privilege) { uint ret = 0; using (Sid sid = new Sid(sidString)) { LSA_UNICODE_STRING[] privileges = new LSA_UNICODE_STRING[1]; privileges[0] = InitLsaString(privilege); ret = LsaRemoveAccountRights(lsaHandle, sid.pSid, false, privileges, 1); } if (ret != 0) throw new Win32Exception(LsaNtStatusToWinError((int)ret)); } public string[] EnumerateAccountsWithUserRight(string privilege) { uint ret = 0; ulong count = 0; LSA_UNICODE_STRING[] rights = new LSA_UNICODE_STRING[1]; rights[0] = InitLsaString(privilege); IntPtr buffer = IntPtr.Zero; ret = LsaEnumerateAccountsWithUserRight(lsaHandle, rights, out buffer, out count); switch (ret) { case 0: string[] accounts = new string[count]; for (int i = 0; i < (int)count; i++) { LSA_ENUMERATION_INFORMATION LsaInfo = (LSA_ENUMERATION_INFORMATION)Marshal.PtrToStructure( IntPtr.Add(buffer, i * Marshal.SizeOf(typeof(LSA_ENUMERATION_INFORMATION))), typeof(LSA_ENUMERATION_INFORMATION)); accounts[i] = new SecurityIdentifier(LsaInfo.Sid).ToString(); } LsaFreeMemory(buffer); return accounts; case STATUS_NO_MORE_ENTRIES: return new string[0]; case STATUS_NO_SUCH_PRIVILEGE: throw new ArgumentException(String.Format("Invalid privilege {0} not found in LSA database", privilege)); default: throw new Win32Exception(LsaNtStatusToWinError((int)ret)); } } static LSA_UNICODE_STRING InitLsaString(string s) { // Unicode strings max. 32KB if (s.Length > 0x7ffe) throw new ArgumentException("String too long"); LSA_UNICODE_STRING lus = new LSA_UNICODE_STRING(); lus.Buffer = s; lus.Length = (ushort)(s.Length * sizeof(char)); lus.MaximumLength = (ushort)(lus.Length + sizeof(char)); return lus; } public void Dispose() { if (lsaHandle != IntPtr.Zero) { LsaClose(lsaHandle); lsaHandle = IntPtr.Zero; } GC.SuppressFinalize(this); } ~LsaRightHelper() { Dispose(); } } } "@ $original_tmp = $env:TMP $env:TMP = $_remote_tmp Add-Type -TypeDefinition $sec_helper_util $env:TMP = $original_tmp Function Compare-UserList($existing_users, $new_users) { $added_users = [String[]]@() $removed_users = [String[]]@() if ($action -eq "add") { $added_users = [Linq.Enumerable]::Except($new_users, $existing_users) } elseif ($action -eq "remove") { $removed_users = [Linq.Enumerable]::Intersect($new_users, $existing_users) } else { $added_users = [Linq.Enumerable]::Except($new_users, $existing_users) $removed_users = [Linq.Enumerable]::Except($existing_users, $new_users) } $change_result = @{ added = $added_users removed = $removed_users } return $change_result } # C# class we can use to enumerate/add/remove rights $lsa_helper = New-Object -TypeName Ansible.LsaRightHelper $new_users = [System.Collections.ArrayList]@() foreach ($user in $users) { $user_sid = Convert-ToSID -account_name $user $new_users.Add($user_sid) > $null } $new_users = [String[]]$new_users.ToArray() try { $existing_users = $lsa_helper.EnumerateAccountsWithUserRight($name) } catch [ArgumentException] { Fail-Json -obj $result -message "the specified right $name is not a valid right" } catch { Fail-Json -obj $result -message "failed to enumerate existing accounts with right: $($_.Exception.Message)" } $change_result = Compare-UserList -existing_users $existing_users -new_user $new_users if (($change_result.added.Length -gt 0) -or ($change_result.removed.Length -gt 0)) { $result.changed = $true $diff_text = "[$name]`n" # used in diff mode calculation $new_user_list = [System.Collections.ArrayList]$existing_users foreach ($user in $change_result.removed) { if (-not $check_mode) { $lsa_helper.RemovePrivilege($user, $name) } $user_name = Convert-FromSID -sid $user $result.removed += $user_name $diff_text += "-$user_name`n" $new_user_list.Remove($user) > $null } foreach ($user in $change_result.added) { if (-not $check_mode) { $lsa_helper.AddPrivilege($user, $name) } $user_name = Convert-FromSID -sid $user $result.added += $user_name $diff_text += "+$user_name`n" $new_user_list.Add($user) > $null } if ($diff_mode) { if ($new_user_list.Count -eq 0) { $diff_text = "-$diff_text" } else { if ($existing_users.Count -eq 0) { $diff_text = "+$diff_text" } } $result.diff.prepared = $diff_text } } Exit-Json $result