350 lines
12 KiB
PowerShell
350 lines
12 KiB
PowerShell
|
#!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
|