win_user_right: add module with tests (#26276)
* win_user_right: add module with tests * fixed up name of module in docs * forgot the test module * fixed up whitespace * changes made to win_user_right based on feedback * moved away from using secedit to Win32 with P/Invoke * tidied up copyright for documentation
This commit is contained in:
parent
3b4ff67b3b
commit
e46adece48
7 changed files with 955 additions and 0 deletions
413
lib/ansible/modules/windows/win_user_right.ps1
Normal file
413
lib/ansible/modules/windows/win_user_right.ps1
Normal file
|
@ -0,0 +1,413 @@
|
||||||
|
#!powershell
|
||||||
|
# This file is part of Ansible
|
||||||
|
|
||||||
|
# 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.psm1
|
||||||
|
$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
|
||||||
|
|
||||||
|
$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 = @{}
|
||||||
|
}
|
||||||
|
|
||||||
|
Add-Type -TypeDefinition @"
|
||||||
|
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(); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"@
|
||||||
|
|
||||||
|
Function Get-Username($sid) {
|
||||||
|
# converts the SID (if it is one) to a username
|
||||||
|
|
||||||
|
$object = New-Object System.Security.Principal.SecurityIdentifier($sid)
|
||||||
|
$user = $object.Translate([System.Security.Principal.NTAccount])
|
||||||
|
return $user.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
Function Get-SID($account_name) {
|
||||||
|
# Can take in the following account name forms and convert to a SID
|
||||||
|
# UPN:
|
||||||
|
# username@domain (Domain)
|
||||||
|
# Down-Level Login Name
|
||||||
|
# domain\username (Domain)
|
||||||
|
# computername\username (Local)
|
||||||
|
# .\username (Local)
|
||||||
|
# Login Name
|
||||||
|
# username (Local)
|
||||||
|
|
||||||
|
if ($account_name -like "*\*") {
|
||||||
|
$account_name_split = $account_name -split "\\"
|
||||||
|
if ($account_name_split[0] -eq ".") {
|
||||||
|
$domain = $env:COMPUTERNAME
|
||||||
|
} else {
|
||||||
|
$domain = $account_name_split[0]
|
||||||
|
}
|
||||||
|
$username = $account_name_split[1]
|
||||||
|
} elseif ($account_name -like "*@*") {
|
||||||
|
$account_name_split = $account_name -split "@"
|
||||||
|
$domain = $account_name_split[1]
|
||||||
|
$username = $account_name_split[0]
|
||||||
|
} else {
|
||||||
|
$domain = $null
|
||||||
|
$username = $account_name
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($domain) {
|
||||||
|
# searching for a local group with the servername prefixed will fail,
|
||||||
|
# need to check for this situation and only use NTAccount(String)
|
||||||
|
if ($domain -eq $env:COMPUTERNAME) {
|
||||||
|
$adsi = [ADSI]("WinNT://$env:COMPUTERNAME,computer")
|
||||||
|
$group = $adsi.psbase.children | Where-Object { $_.schemaClassName -eq "group" } | Where-Object { $_.Name -eq $username }
|
||||||
|
} else {
|
||||||
|
$group = $null
|
||||||
|
}
|
||||||
|
if ($group) {
|
||||||
|
$account = New-Object System.Security.Principal.NTAccount($username)
|
||||||
|
} else {
|
||||||
|
$account = New-Object System.Security.Principal.NTAccount($domain, $username)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
# when in a domain NTAccount(String) will favour domain lookups check
|
||||||
|
# if username is a local user and explictly search on the localhost for
|
||||||
|
# that account
|
||||||
|
$adsi = [ADSI]("WinNT://$env:COMPUTERNAME,computer")
|
||||||
|
$user = $adsi.psbase.children | Where-Object { $_.schemaClassName -eq "user" } | Where-Object { $_.Name -eq $username }
|
||||||
|
if ($user) {
|
||||||
|
$account = New-Object System.Security.Principal.NTAccount($env:COMPUTERNAME, $username)
|
||||||
|
} else {
|
||||||
|
$account = New-Object System.Security.Principal.NTAccount($username)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$account_sid = $account.Translate([System.Security.Principal.SecurityIdentifier])
|
||||||
|
} catch {
|
||||||
|
Fail-Json $result "Account Name: $account_name is not a valid account, cannot get SID: $($_.Exception.Message)"
|
||||||
|
}
|
||||||
|
|
||||||
|
return $account_sid.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
$new_users.Add((Get-SID -account_name $user))
|
||||||
|
}
|
||||||
|
$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 = Get-Username -sid $user
|
||||||
|
$result.removed += $user_name
|
||||||
|
$diff_text += "-$user_name`n"
|
||||||
|
$new_user_list.Remove($user)
|
||||||
|
}
|
||||||
|
foreach ($user in $change_result.added) {
|
||||||
|
if (-not $check_mode) {
|
||||||
|
$lsa_helper.AddPrivilege($user, $name)
|
||||||
|
}
|
||||||
|
$user_name = Get-Username -sid $user
|
||||||
|
$result.added += $user_name
|
||||||
|
$diff_text += "+$user_name`n"
|
||||||
|
$new_user_list.Add($user)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
97
lib/ansible/modules/windows/win_user_right.py
Normal file
97
lib/ansible/modules/windows/win_user_right.py
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# This file is part of Ansible
|
||||||
|
|
||||||
|
# Copyright (c) 2017 Ansible Project
|
||||||
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
|
|
||||||
|
# this is a windows documentation stub. actual code lives in the .ps1
|
||||||
|
# file of the same name
|
||||||
|
|
||||||
|
ANSIBLE_METADATA = {'metadata_version': '1.0',
|
||||||
|
'status': ['preview'],
|
||||||
|
'supported_by': 'community'}
|
||||||
|
|
||||||
|
|
||||||
|
DOCUMENTATION = r'''
|
||||||
|
---
|
||||||
|
module: win_user_right
|
||||||
|
version_added: '2.4'
|
||||||
|
short_description: Manage Windows User Rights
|
||||||
|
description:
|
||||||
|
- Add, remove or set User Rights for a group or users or groups.
|
||||||
|
- You can set user rights for both local and domain accounts.
|
||||||
|
notes:
|
||||||
|
- If the server is domain joined this module can change a right but if a GPO
|
||||||
|
governs this right then the changes won't last.
|
||||||
|
options:
|
||||||
|
name:
|
||||||
|
description:
|
||||||
|
- The name of the User Right as shown by the C(Constant Name) value from
|
||||||
|
U(https://technet.microsoft.com/en-us/library/dd349804.aspx).
|
||||||
|
- The module will return an error if the right is invalid.
|
||||||
|
required: True
|
||||||
|
users:
|
||||||
|
description:
|
||||||
|
- A list of users or groups to add/remove on the User Right.
|
||||||
|
- These can be in the form DOMAIN\user-group, user-group@DOMAIN.COM for
|
||||||
|
domain users/groups.
|
||||||
|
- For local users/groups it can be in the form user-group, .\user-group,
|
||||||
|
SERVERNAME\user-group where SERVERNAME is the name of the remote server.
|
||||||
|
- You can also add special local accounts like SYSTEM and others.
|
||||||
|
required: True
|
||||||
|
action:
|
||||||
|
description:
|
||||||
|
- C(add) will add the users/groups to the existing right.
|
||||||
|
- C(remove) will remove the users/groups from the existing right.
|
||||||
|
- C(set) will replace the users/groups of the existing right.
|
||||||
|
default: set
|
||||||
|
choices: [set, add, remove]
|
||||||
|
author:
|
||||||
|
- Jordan Borean (@jborean93)
|
||||||
|
'''
|
||||||
|
|
||||||
|
EXAMPLES = r'''
|
||||||
|
---
|
||||||
|
- name: replace the entries of Deny log on locally
|
||||||
|
win_user_right:
|
||||||
|
name: SeDenyInteractiveLogonRight
|
||||||
|
users:
|
||||||
|
- Guest
|
||||||
|
- Users
|
||||||
|
action: set
|
||||||
|
|
||||||
|
- name: add account to Log on as a service
|
||||||
|
win_user_right:
|
||||||
|
name: SeServiceLogonRight
|
||||||
|
users:
|
||||||
|
- .\Administrator
|
||||||
|
- '{{ansible_hostname}}\local-user'
|
||||||
|
action: add
|
||||||
|
|
||||||
|
- name: remove accounts who can create Symbolic links
|
||||||
|
win_user_right:
|
||||||
|
name: SeCreateSymbolicLinkPrivilege
|
||||||
|
users:
|
||||||
|
- SYSTEM
|
||||||
|
- Administrators
|
||||||
|
- DOMAIN\User
|
||||||
|
- group@DOMAIN.COM
|
||||||
|
action: remove
|
||||||
|
'''
|
||||||
|
|
||||||
|
RETURN = r'''
|
||||||
|
added:
|
||||||
|
description: A list of accounts that were added to the right, this is empty
|
||||||
|
if no accounts were added.
|
||||||
|
returned: success
|
||||||
|
type: list
|
||||||
|
sample: ["NT AUTHORITY\\SYSTEM", "DOMAIN\\User"]
|
||||||
|
removed:
|
||||||
|
description: A list of accounts that were removed from the right, this is
|
||||||
|
empty if no accounts were removed.
|
||||||
|
returned: success
|
||||||
|
type: list
|
||||||
|
sample: ["SERVERNAME\\Administrator", "BUILTIN\\Administrators"]
|
||||||
|
'''
|
1
test/integration/targets/win_user_right/aliases
Normal file
1
test/integration/targets/win_user_right/aliases
Normal file
|
@ -0,0 +1 @@
|
||||||
|
windows/ci/group1
|
|
@ -0,0 +1 @@
|
||||||
|
test_win_user_right_name: SeRelabelPrivilege
|
|
@ -0,0 +1,44 @@
|
||||||
|
#!powershell
|
||||||
|
|
||||||
|
#Requires -Module Ansible.ModuleUtils.Legacy.psm1
|
||||||
|
|
||||||
|
# basic script to get the lsit of users in a particular right
|
||||||
|
# this is quite complex to put as a simple script so this is
|
||||||
|
# just a simple module
|
||||||
|
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
|
||||||
|
$params = Parse-Args $args -supports_check_mode $false
|
||||||
|
$name = Get-AnsibleParam -obj $params -name "name" -type "str" -failifempty $true
|
||||||
|
|
||||||
|
$result = @{
|
||||||
|
changed = $false
|
||||||
|
users = @()
|
||||||
|
}
|
||||||
|
|
||||||
|
Function Get-Username($sid) {
|
||||||
|
$object = New-Object System.Security.Principal.SecurityIdentifier($sid)
|
||||||
|
$user = $object.Translate([System.Security.Principal.NTAccount])
|
||||||
|
$user.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
$secedit_ini_path = [IO.Path]::GetTempFileName()
|
||||||
|
&SecEdit.exe /export /cfg $secedit_ini_path /quiet
|
||||||
|
$secedit_ini = Get-Content -Path $secedit_ini_path
|
||||||
|
Remove-Item -Path $secedit_ini_path -Force
|
||||||
|
|
||||||
|
foreach ($line in $secedit_ini) {
|
||||||
|
if ($line.ToLower().StartsWith("$($name.ToLower()) = ")) {
|
||||||
|
$right_split = $line -split "="
|
||||||
|
$existing_users = $right_split[-1].Trim() -split ","
|
||||||
|
foreach ($user in $existing_users) {
|
||||||
|
if ($user.StartsWith("*S")) {
|
||||||
|
$result.users += Get-Username -sid $user.substring(1)
|
||||||
|
} else {
|
||||||
|
$result.users += $user
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Exit-Json $result
|
25
test/integration/targets/win_user_right/tasks/main.yml
Normal file
25
test/integration/targets/win_user_right/tasks/main.yml
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
---
|
||||||
|
- name: get current entries for right
|
||||||
|
test_get_right:
|
||||||
|
name: '{{test_win_user_right_name}}'
|
||||||
|
register: actual_users
|
||||||
|
|
||||||
|
- name: get facts
|
||||||
|
setup:
|
||||||
|
|
||||||
|
- block:
|
||||||
|
- name: ensure right is empty before test
|
||||||
|
win_user_right:
|
||||||
|
name: '{{test_win_user_right_name}}'
|
||||||
|
users: []
|
||||||
|
action: set
|
||||||
|
|
||||||
|
- name: run tests
|
||||||
|
include_tasks: tests.yml
|
||||||
|
|
||||||
|
always:
|
||||||
|
- name: reset entries for test right
|
||||||
|
win_user_right:
|
||||||
|
name: '{{test_win_user_right_name}}'
|
||||||
|
users: '{{actual_users.users}}'
|
||||||
|
action: set
|
374
test/integration/targets/win_user_right/tasks/tests.yml
Normal file
374
test/integration/targets/win_user_right/tasks/tests.yml
Normal file
|
@ -0,0 +1,374 @@
|
||||||
|
---
|
||||||
|
- name: fail to set invalid right
|
||||||
|
win_user_right:
|
||||||
|
name: FailRight
|
||||||
|
users: Administrator
|
||||||
|
register: fail_invalid_right
|
||||||
|
failed_when: fail_invalid_right.msg != 'the specified right FailRight is not a valid right'
|
||||||
|
|
||||||
|
- name: fail with invalid username
|
||||||
|
win_user_right:
|
||||||
|
name: '{{test_win_user_right_name}}'
|
||||||
|
users: FakeUser
|
||||||
|
register: fail_invalid_user
|
||||||
|
failed_when: "'Account Name: FakeUser is not a valid account, cannot get SID' not in fail_invalid_user.msg"
|
||||||
|
|
||||||
|
- name: remove from empty right check
|
||||||
|
win_user_right:
|
||||||
|
name: '{{test_win_user_right_name}}'
|
||||||
|
users: ['Administrator', 'Administrators']
|
||||||
|
action: remove
|
||||||
|
register: remove_empty_right_check
|
||||||
|
check_mode: yes
|
||||||
|
|
||||||
|
- name: assert remove from empty right check
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- not remove_empty_right_check|changed
|
||||||
|
- remove_empty_right_check.added == []
|
||||||
|
- remove_empty_right_check.removed == []
|
||||||
|
|
||||||
|
- name: remove from empty right
|
||||||
|
win_user_right:
|
||||||
|
name: '{{test_win_user_right_name}}'
|
||||||
|
users: ['Administrator', 'Administrators']
|
||||||
|
action: remove
|
||||||
|
register: remove_empty_right
|
||||||
|
check_mode: yes
|
||||||
|
|
||||||
|
- name: assert remove from empty right
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- not remove_empty_right|changed
|
||||||
|
- remove_empty_right.added == []
|
||||||
|
- remove_empty_right.removed == []
|
||||||
|
|
||||||
|
- name: set administrator check
|
||||||
|
win_user_right:
|
||||||
|
name: '{{test_win_user_right_name}}'
|
||||||
|
users: Administrator
|
||||||
|
action: set
|
||||||
|
register: set_administrator_check
|
||||||
|
check_mode: yes
|
||||||
|
|
||||||
|
- name: get actual set administrator check
|
||||||
|
test_get_right:
|
||||||
|
name: '{{test_win_user_right_name}}'
|
||||||
|
register: set_administrator_actual_check
|
||||||
|
|
||||||
|
- name: assert set administrator check
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- set_administrator_check|changed
|
||||||
|
- set_administrator_check.added == ["{{ansible_hostname}}\\Administrator"]
|
||||||
|
- set_administrator_check.removed == []
|
||||||
|
- set_administrator_actual_check.users == []
|
||||||
|
|
||||||
|
- name: set administrator
|
||||||
|
win_user_right:
|
||||||
|
name: '{{test_win_user_right_name}}'
|
||||||
|
users: Administrator
|
||||||
|
action: set
|
||||||
|
register: set_administrator
|
||||||
|
|
||||||
|
- name: get actual set administrator
|
||||||
|
test_get_right:
|
||||||
|
name: '{{test_win_user_right_name}}'
|
||||||
|
register: set_administrator_actual
|
||||||
|
|
||||||
|
- name: assert set administrator check
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- set_administrator|changed
|
||||||
|
- set_administrator.added == ["{{ansible_hostname}}\\Administrator"]
|
||||||
|
- set_administrator.removed == []
|
||||||
|
- set_administrator_actual.users == ['Administrator']
|
||||||
|
|
||||||
|
- name: set administrator again
|
||||||
|
win_user_right:
|
||||||
|
name: '{{test_win_user_right_name}}'
|
||||||
|
users: Administrator
|
||||||
|
action: set
|
||||||
|
register: set_administrator_again
|
||||||
|
|
||||||
|
- name: assert set administrator check
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- not set_administrator_again|changed
|
||||||
|
- set_administrator_again.added == []
|
||||||
|
- set_administrator_again.removed == []
|
||||||
|
|
||||||
|
- name: remove from right check
|
||||||
|
win_user_right:
|
||||||
|
name: '{{test_win_user_right_name}}'
|
||||||
|
users: ['Administrator', 'Guests', '{{ansible_hostname}}\Users', '.\Backup Operators']
|
||||||
|
action: remove
|
||||||
|
register: remove_right_check
|
||||||
|
check_mode: yes
|
||||||
|
|
||||||
|
- name: get actual remove from right check
|
||||||
|
test_get_right:
|
||||||
|
name: '{{test_win_user_right_name}}'
|
||||||
|
register: remove_right_actual_check
|
||||||
|
|
||||||
|
- name: assert remove from right check
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- remove_right_check|changed
|
||||||
|
- remove_right_check.removed == ["{{ansible_hostname}}\\Administrator"]
|
||||||
|
- remove_right_check.added == []
|
||||||
|
- remove_right_actual_check.users == ['Administrator']
|
||||||
|
|
||||||
|
- name: remove from right
|
||||||
|
win_user_right:
|
||||||
|
name: '{{test_win_user_right_name}}'
|
||||||
|
users: ['Administrator', 'Guests', '{{ansible_hostname}}\Users', '.\Backup Operators']
|
||||||
|
action: remove
|
||||||
|
register: remove_right
|
||||||
|
|
||||||
|
- name: get actual remove from right
|
||||||
|
test_get_right:
|
||||||
|
name: '{{test_win_user_right_name}}'
|
||||||
|
register: remove_right_actual
|
||||||
|
|
||||||
|
- name: assert remove from right
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- remove_right|changed
|
||||||
|
- remove_right.removed == ["{{ansible_hostname}}\\Administrator"]
|
||||||
|
- remove_right.added == []
|
||||||
|
- remove_right_actual.users == []
|
||||||
|
|
||||||
|
- name: remove from right again
|
||||||
|
win_user_right:
|
||||||
|
name: '{{test_win_user_right_name}}'
|
||||||
|
users: ['Administrator', 'Guests', '{{ansible_hostname}}\Users', '.\Backup Operators']
|
||||||
|
action: remove
|
||||||
|
register: remove_right_again
|
||||||
|
|
||||||
|
- name: assert remove from right
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- not remove_right_again|changed
|
||||||
|
- remove_right_again.removed == []
|
||||||
|
- remove_right_again.added == []
|
||||||
|
|
||||||
|
- name: add to empty right check
|
||||||
|
win_user_right:
|
||||||
|
name: '{{test_win_user_right_name}}'
|
||||||
|
users: ['Administrator', 'Administrators']
|
||||||
|
action: add
|
||||||
|
register: add_right_on_empty_check
|
||||||
|
check_mode: yes
|
||||||
|
|
||||||
|
- name: get actual add to empty right check
|
||||||
|
test_get_right:
|
||||||
|
name: '{{test_win_user_right_name}}'
|
||||||
|
register: add_right_on_empty_actual_check
|
||||||
|
|
||||||
|
- name: assert add to empty right check
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- add_right_on_empty_check|changed
|
||||||
|
- add_right_on_empty_check.removed == []
|
||||||
|
- add_right_on_empty_check.added == ["{{ansible_hostname}}\\Administrator", "BUILTIN\\Administrators"]
|
||||||
|
- add_right_on_empty_actual_check.users == []
|
||||||
|
|
||||||
|
- name: add to empty right
|
||||||
|
win_user_right:
|
||||||
|
name: '{{test_win_user_right_name}}'
|
||||||
|
users: ['Administrator', 'Administrators']
|
||||||
|
action: add
|
||||||
|
register: add_right_on_empty
|
||||||
|
|
||||||
|
- name: get actual add to empty right
|
||||||
|
test_get_right:
|
||||||
|
name: '{{test_win_user_right_name}}'
|
||||||
|
register: add_right_on_empty_actual
|
||||||
|
|
||||||
|
- name: assert add to empty right
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- add_right_on_empty|changed
|
||||||
|
- add_right_on_empty.removed == []
|
||||||
|
- add_right_on_empty.added == ["{{ansible_hostname}}\\Administrator", "BUILTIN\\Administrators"]
|
||||||
|
- add_right_on_empty_actual.users == ["Administrator", "BUILTIN\\Administrators"]
|
||||||
|
|
||||||
|
- name: add to empty right again
|
||||||
|
win_user_right:
|
||||||
|
name: '{{test_win_user_right_name}}'
|
||||||
|
users: ['Administrator', 'Administrators']
|
||||||
|
action: add
|
||||||
|
register: add_right_on_empty_again
|
||||||
|
|
||||||
|
- name: assert add to empty right
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- not add_right_on_empty_again|changed
|
||||||
|
- add_right_on_empty_again.removed == []
|
||||||
|
- add_right_on_empty_again.added == []
|
||||||
|
|
||||||
|
- name: add to existing right check
|
||||||
|
win_user_right:
|
||||||
|
name: '{{test_win_user_right_name}}'
|
||||||
|
users: ['Administrator', 'Guests', '{{ansible_hostname}}\Users']
|
||||||
|
action: add
|
||||||
|
register: add_right_on_existing_check
|
||||||
|
check_mode: yes
|
||||||
|
|
||||||
|
- name: get actual add to existing right check
|
||||||
|
test_get_right:
|
||||||
|
name: '{{test_win_user_right_name}}'
|
||||||
|
register: add_right_on_existing_actual_check
|
||||||
|
|
||||||
|
- name: assert add to existing right check
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- add_right_on_existing_check|changed
|
||||||
|
- add_right_on_existing_check.removed == []
|
||||||
|
- add_right_on_existing_check.added == ["BUILTIN\\Guests", "BUILTIN\\Users"]
|
||||||
|
- add_right_on_existing_actual_check.users == ["Administrator", "BUILTIN\\Administrators"]
|
||||||
|
|
||||||
|
- name: add to existing right
|
||||||
|
win_user_right:
|
||||||
|
name: '{{test_win_user_right_name}}'
|
||||||
|
users: ['Administrator', 'Guests', '{{ansible_hostname}}\Users']
|
||||||
|
action: add
|
||||||
|
register: add_right_on_existing
|
||||||
|
|
||||||
|
- name: get actual add to existing right
|
||||||
|
test_get_right:
|
||||||
|
name: '{{test_win_user_right_name}}'
|
||||||
|
register: add_right_on_existing_actual
|
||||||
|
|
||||||
|
- name: assert add to existing right
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- add_right_on_existing|changed
|
||||||
|
- add_right_on_existing.removed == []
|
||||||
|
- add_right_on_existing.added == ["BUILTIN\\Guests", "BUILTIN\\Users"]
|
||||||
|
- add_right_on_existing_actual.users == ["Administrator", "BUILTIN\\Administrators", "BUILTIN\\Users", "BUILTIN\\Guests"]
|
||||||
|
|
||||||
|
- name: add to existing right again
|
||||||
|
win_user_right:
|
||||||
|
name: '{{test_win_user_right_name}}'
|
||||||
|
users: ['Administrator', 'Guests', '{{ansible_hostname}}\Users']
|
||||||
|
action: add
|
||||||
|
register: add_right_on_existing_again
|
||||||
|
|
||||||
|
- name: assert add to existing right
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- not add_right_on_existing_again|changed
|
||||||
|
- add_right_on_existing_again.removed == []
|
||||||
|
- add_right_on_existing_again.added == []
|
||||||
|
|
||||||
|
- name: remove from existing check
|
||||||
|
win_user_right:
|
||||||
|
name: '{{test_win_user_right_name}}'
|
||||||
|
users: ['Guests', 'Administrator']
|
||||||
|
action: remove
|
||||||
|
register: remove_on_existing_check
|
||||||
|
check_mode: yes
|
||||||
|
|
||||||
|
- name: get actual remove from existing check
|
||||||
|
test_get_right:
|
||||||
|
name: '{{test_win_user_right_name}}'
|
||||||
|
register: remove_on_existing_actual_check
|
||||||
|
|
||||||
|
- name: assert remove from existing check
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- remove_on_existing_check|changed
|
||||||
|
- remove_on_existing_check.removed == ["BUILTIN\\Guests", "{{ansible_hostname}}\\Administrator"]
|
||||||
|
- remove_on_existing_check.added == []
|
||||||
|
- remove_on_existing_actual_check.users == ["Administrator", "BUILTIN\\Administrators", "BUILTIN\\Users", "BUILTIN\\Guests"]
|
||||||
|
|
||||||
|
- name: remove from existing
|
||||||
|
win_user_right:
|
||||||
|
name: '{{test_win_user_right_name}}'
|
||||||
|
users: ['Guests', 'Administrator']
|
||||||
|
action: remove
|
||||||
|
register: remove_on_existing
|
||||||
|
|
||||||
|
- name: get actual remove from existing
|
||||||
|
test_get_right:
|
||||||
|
name: '{{test_win_user_right_name}}'
|
||||||
|
register: remove_on_existing_actual
|
||||||
|
|
||||||
|
- name: assert remove from existing
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- remove_on_existing|changed
|
||||||
|
- remove_on_existing.removed == ["BUILTIN\\Guests", "{{ansible_hostname}}\\Administrator"]
|
||||||
|
- remove_on_existing.added == []
|
||||||
|
- remove_on_existing_actual.users == ["BUILTIN\\Administrators", "BUILTIN\\Users"]
|
||||||
|
|
||||||
|
- name: remove from existing again
|
||||||
|
win_user_right:
|
||||||
|
name: '{{test_win_user_right_name}}'
|
||||||
|
users: ['Guests', 'Administrator']
|
||||||
|
action: remove
|
||||||
|
register: remove_on_existing_again
|
||||||
|
|
||||||
|
- name: assert remove from existing again
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- not remove_on_existing_again|changed
|
||||||
|
- remove_on_existing_again.removed == []
|
||||||
|
- remove_on_existing_again.added == []
|
||||||
|
|
||||||
|
- name: set to existing check
|
||||||
|
win_user_right:
|
||||||
|
name: '{{test_win_user_right_name}}'
|
||||||
|
users: ['Administrators', 'SYSTEM', 'Backup Operators']
|
||||||
|
action: set
|
||||||
|
register: set_on_existing_check
|
||||||
|
check_mode: yes
|
||||||
|
|
||||||
|
- name: get actual set to existing check
|
||||||
|
test_get_right:
|
||||||
|
name: '{{test_win_user_right_name}}'
|
||||||
|
register: set_on_existing_actual_check
|
||||||
|
|
||||||
|
- name: assert set to existing check
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- set_on_existing_check|changed
|
||||||
|
- set_on_existing_check.removed == ["BUILTIN\\Users"]
|
||||||
|
- set_on_existing_check.added == ["NT AUTHORITY\\SYSTEM", "BUILTIN\\Backup Operators"]
|
||||||
|
- set_on_existing_actual_check.users == ["BUILTIN\\Administrators", "BUILTIN\\Users"]
|
||||||
|
|
||||||
|
- name: set to existing
|
||||||
|
win_user_right:
|
||||||
|
name: '{{test_win_user_right_name}}'
|
||||||
|
users: ['Administrators', 'SYSTEM', 'Backup Operators']
|
||||||
|
action: set
|
||||||
|
register: set_on_existing
|
||||||
|
|
||||||
|
- name: get actual set to existing
|
||||||
|
test_get_right:
|
||||||
|
name: '{{test_win_user_right_name}}'
|
||||||
|
register: set_on_existing_actual
|
||||||
|
|
||||||
|
- name: assert set to existing
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- set_on_existing|changed
|
||||||
|
- set_on_existing.removed == ["BUILTIN\\Users"]
|
||||||
|
- set_on_existing.added == ["NT AUTHORITY\\SYSTEM", "BUILTIN\\Backup Operators"]
|
||||||
|
- set_on_existing_actual.users == ["NT AUTHORITY\\SYSTEM", "BUILTIN\\Administrators", "BUILTIN\\Backup Operators"]
|
||||||
|
|
||||||
|
- name: set to existing again
|
||||||
|
win_user_right:
|
||||||
|
name: '{{test_win_user_right_name}}'
|
||||||
|
users: ['Administrators', 'SYSTEM', 'Backup Operators']
|
||||||
|
action: set
|
||||||
|
register: set_on_existing_again
|
||||||
|
|
||||||
|
- name: assert set to existing
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- not set_on_existing_again|changed
|
||||||
|
- set_on_existing_again.removed == []
|
||||||
|
- set_on_existing_again.added == []
|
Loading…
Reference in a new issue