mirror of https://github.com/ansible/ansible.git
win_auto_logon - check, diff and store pass in LSA (#65528)
* win_auto_logon - check, diff and store pass in LSA * Ensure baseline keys are set for test * Skip remove item prop on check mode due to win bug * Start at a cleared baseline to ensure old LSA secrets are clearedpull/65530/head
parent
cff80f1319
commit
4d3ebd65db
@ -0,0 +1,3 @@
|
||||
# This doesn't have to be valid, just testing weird chars in the pass
|
||||
test_logon_password: 'café - 💩'
|
||||
test_logon_password2: '.ÅÑŚÌβŁÈ [$!@^&test(;)]'
|
||||
@ -0,0 +1,214 @@
|
||||
#!powershell
|
||||
|
||||
#AnsibleRequires -CSharpUtil Ansible.Basic
|
||||
#Requires -Module Ansible.ModuleUtils.AddType
|
||||
|
||||
$module = [Ansible.Basic.AnsibleModule]::Create($args, @{})
|
||||
|
||||
Add-CSharpType -AnsibleModule $module -References @'
|
||||
using Microsoft.Win32.SafeHandles;
|
||||
using System;
|
||||
using System.Runtime.ConstrainedExecution;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace Ansible.TestAutoLogonInfo
|
||||
{
|
||||
internal class NativeHelpers
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public class LSA_OBJECT_ATTRIBUTES
|
||||
{
|
||||
public UInt32 Length = 0;
|
||||
public IntPtr RootDirectory = IntPtr.Zero;
|
||||
public IntPtr ObjectName = IntPtr.Zero;
|
||||
public UInt32 Attributes = 0;
|
||||
public IntPtr SecurityDescriptor = IntPtr.Zero;
|
||||
public IntPtr SecurityQualityOfService = IntPtr.Zero;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||
internal struct LSA_UNICODE_STRING
|
||||
{
|
||||
public UInt16 Length;
|
||||
public UInt16 MaximumLength;
|
||||
public IntPtr Buffer;
|
||||
|
||||
public static explicit operator string(LSA_UNICODE_STRING s)
|
||||
{
|
||||
byte[] strBytes = new byte[s.Length];
|
||||
Marshal.Copy(s.Buffer, strBytes, 0, s.Length);
|
||||
return Encoding.Unicode.GetString(strBytes);
|
||||
}
|
||||
|
||||
public static SafeMemoryBuffer CreateSafeBuffer(string s)
|
||||
{
|
||||
if (s == null)
|
||||
return new SafeMemoryBuffer(IntPtr.Zero);
|
||||
|
||||
byte[] stringBytes = Encoding.Unicode.GetBytes(s);
|
||||
int structSize = Marshal.SizeOf(typeof(LSA_UNICODE_STRING));
|
||||
IntPtr buffer = Marshal.AllocHGlobal(structSize + stringBytes.Length);
|
||||
try
|
||||
{
|
||||
LSA_UNICODE_STRING lsaString = new LSA_UNICODE_STRING()
|
||||
{
|
||||
Length = (UInt16)(stringBytes.Length),
|
||||
MaximumLength = (UInt16)(stringBytes.Length),
|
||||
Buffer = IntPtr.Add(buffer, structSize),
|
||||
};
|
||||
Marshal.StructureToPtr(lsaString, buffer, false);
|
||||
Marshal.Copy(stringBytes, 0, lsaString.Buffer, stringBytes.Length);
|
||||
return new SafeMemoryBuffer(buffer);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Make sure we free the pointer before raising the exception.
|
||||
Marshal.FreeHGlobal(buffer);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class NativeMethods
|
||||
{
|
||||
[DllImport("Advapi32.dll")]
|
||||
public static extern UInt32 LsaClose(
|
||||
IntPtr ObjectHandle);
|
||||
|
||||
[DllImport("Advapi32.dll")]
|
||||
public static extern UInt32 LsaFreeMemory(
|
||||
IntPtr Buffer);
|
||||
|
||||
[DllImport("Advapi32.dll")]
|
||||
internal static extern Int32 LsaNtStatusToWinError(
|
||||
UInt32 Status);
|
||||
|
||||
[DllImport("Advapi32.dll")]
|
||||
public static extern UInt32 LsaOpenPolicy(
|
||||
IntPtr SystemName,
|
||||
NativeHelpers.LSA_OBJECT_ATTRIBUTES ObjectAttributes,
|
||||
UInt32 AccessMask,
|
||||
out SafeLsaHandle PolicyHandle);
|
||||
|
||||
[DllImport("Advapi32.dll")]
|
||||
public static extern UInt32 LsaRetrievePrivateData(
|
||||
SafeLsaHandle PolicyHandle,
|
||||
SafeMemoryBuffer KeyName,
|
||||
out SafeLsaMemory PrivateData);
|
||||
}
|
||||
|
||||
internal class SafeLsaMemory : SafeBuffer
|
||||
{
|
||||
internal SafeLsaMemory() : base(true) { }
|
||||
|
||||
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
|
||||
|
||||
protected override bool ReleaseHandle()
|
||||
{
|
||||
return NativeMethods.LsaFreeMemory(handle) == 0;
|
||||
}
|
||||
}
|
||||
|
||||
internal class SafeMemoryBuffer : SafeBuffer
|
||||
{
|
||||
internal SafeMemoryBuffer() : base(true) { }
|
||||
|
||||
internal SafeMemoryBuffer(IntPtr ptr) : base(true)
|
||||
{
|
||||
base.SetHandle(ptr);
|
||||
}
|
||||
|
||||
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
|
||||
|
||||
protected override bool ReleaseHandle()
|
||||
{
|
||||
if (handle != IntPtr.Zero)
|
||||
Marshal.FreeHGlobal(handle);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public class SafeLsaHandle : SafeHandleZeroOrMinusOneIsInvalid
|
||||
{
|
||||
internal SafeLsaHandle() : base(true) { }
|
||||
|
||||
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
|
||||
|
||||
protected override bool ReleaseHandle()
|
||||
{
|
||||
return NativeMethods.LsaClose(handle) == 0;
|
||||
}
|
||||
}
|
||||
|
||||
public class Win32Exception : System.ComponentModel.Win32Exception
|
||||
{
|
||||
private string _exception_msg;
|
||||
public Win32Exception(string message) : this(Marshal.GetLastWin32Error(), message) { }
|
||||
public Win32Exception(int errorCode, string message) : base(errorCode)
|
||||
{
|
||||
_exception_msg = String.Format("{0} - {1} (Win32 Error Code {2}: 0x{3})", message, base.Message, errorCode, errorCode.ToString("X8"));
|
||||
}
|
||||
public override string Message { get { return _exception_msg; } }
|
||||
public static explicit operator Win32Exception(string message) { return new Win32Exception(message); }
|
||||
}
|
||||
|
||||
public class LsaUtil
|
||||
{
|
||||
public static SafeLsaHandle OpenPolicy(UInt32 access)
|
||||
{
|
||||
NativeHelpers.LSA_OBJECT_ATTRIBUTES oa = new NativeHelpers.LSA_OBJECT_ATTRIBUTES();
|
||||
SafeLsaHandle lsaHandle;
|
||||
UInt32 res = NativeMethods.LsaOpenPolicy(IntPtr.Zero, oa, access, out lsaHandle);
|
||||
if (res != 0)
|
||||
throw new Win32Exception(NativeMethods.LsaNtStatusToWinError(res),
|
||||
String.Format("LsaOpenPolicy({0}) failed", access.ToString()));
|
||||
return lsaHandle;
|
||||
}
|
||||
|
||||
public static string RetrievePrivateData(SafeLsaHandle handle, string key)
|
||||
{
|
||||
using (SafeMemoryBuffer keyBuffer = NativeHelpers.LSA_UNICODE_STRING.CreateSafeBuffer(key))
|
||||
{
|
||||
SafeLsaMemory buffer;
|
||||
UInt32 res = NativeMethods.LsaRetrievePrivateData(handle, keyBuffer, out buffer);
|
||||
using (buffer)
|
||||
{
|
||||
if (res != 0)
|
||||
{
|
||||
// If the data object was not found we return null to indicate it isn't set.
|
||||
if (res == 0xC0000034) // STATUS_OBJECT_NAME_NOT_FOUND
|
||||
return null;
|
||||
|
||||
throw new Win32Exception(NativeMethods.LsaNtStatusToWinError(res),
|
||||
String.Format("LsaRetrievePrivateData({0}) failed", key));
|
||||
}
|
||||
|
||||
NativeHelpers.LSA_UNICODE_STRING lsaString = (NativeHelpers.LSA_UNICODE_STRING)
|
||||
Marshal.PtrToStructure(buffer.DangerousGetHandle(),
|
||||
typeof(NativeHelpers.LSA_UNICODE_STRING));
|
||||
return (string)lsaString;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
'@
|
||||
|
||||
$details = Get-ItemProperty -LiteralPath 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon'
|
||||
$module.Result.AutoAdminLogon = $details.AutoAdminLogon
|
||||
$module.Result.DefaultUserName = $details.DefaultUserName
|
||||
$module.Result.DefaultDomainName = $details.DefaultDomainName
|
||||
$module.Result.DefaultPassword = $details.DefaultPassword
|
||||
$module.Result.AutoLogonCount = $details.AutoLogonCount
|
||||
|
||||
$handle = [Ansible.TestAutoLogonInfo.LsaUtil]::OpenPolicy(0x00000004)
|
||||
try {
|
||||
$password = [Ansible.TestAutoLogonInfo.LsaUtil]::RetrievePrivateData($handle, 'DefaultPassword')
|
||||
$module.Result.LsaPassword = $password
|
||||
} finally {
|
||||
$handle.Dispose()
|
||||
}
|
||||
|
||||
$module.ExitJson()
|
||||
@ -1,36 +1,42 @@
|
||||
# Copyright: (c) 2019, Prasoon Karunan V (@prasoonkarunan) <kvprasoon@Live.in>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
---
|
||||
- name: Set autologon registry keys
|
||||
win_auto_logon:
|
||||
username: "{{ ansible_user }}"
|
||||
password: "{{ ansible_password }}"
|
||||
state: present
|
||||
register: win_auto_logon_create_registry_key_set
|
||||
- name: get user domain split for ansible_user
|
||||
win_shell: |
|
||||
$account = New-Object -TypeName System.Security.Principal.NTAccount -ArgumentList '{{ ansible_user }}'
|
||||
$sid = $account.Translate([System.Security.Principal.SecurityIdentifier])
|
||||
$sid.Translate([System.Security.Principal.NTAccount]).Value -split '{{ "\\" }}'
|
||||
changed_when: False
|
||||
register: test_user_split
|
||||
|
||||
- name: check win_auto_logon_create_registry_key_set is changed
|
||||
assert:
|
||||
that:
|
||||
- win_auto_logon_create_registry_key_set is changed
|
||||
- set_fact:
|
||||
test_domain: '{{ test_user_split.stdout_lines[0] }}'
|
||||
test_user: '{{ test_user_split.stdout_lines[1] }}'
|
||||
|
||||
- name: Set autologon registry keys with missing input
|
||||
- name: ensure auto logon is cleared before test
|
||||
win_auto_logon:
|
||||
username: "{{ ansible_user }}"
|
||||
state: present
|
||||
register: win_auto_logon_create_registry_key_missing_input
|
||||
ignore_errors: true
|
||||
state: absent
|
||||
|
||||
- name: check win_auto_logon_create_registry_key_missing_input is failed
|
||||
assert:
|
||||
that:
|
||||
- win_auto_logon_create_registry_key_missing_input is failed
|
||||
- name: ensure defaults are set
|
||||
win_regedit:
|
||||
path: HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon
|
||||
name: '{{ item.name }}'
|
||||
data: '{{ item.value }}'
|
||||
type: '{{ item.type }}'
|
||||
state: present
|
||||
loop:
|
||||
# We set the DefaultPassword to ensure win_auto_logon clears this out
|
||||
- name: DefaultPassword
|
||||
value: abc
|
||||
type: string
|
||||
# Ensures the host we test on has a baseline key to check against
|
||||
- name: AutoAdminLogon
|
||||
value: 0
|
||||
type: dword
|
||||
|
||||
- name: Remove autologon registry keys
|
||||
win_auto_logon:
|
||||
state: absent
|
||||
register: win_auto_logon_create_registry_key_remove
|
||||
- block:
|
||||
- name: run tests
|
||||
include_tasks: tests.yml
|
||||
|
||||
- name: check win_auto_logon_create_registry_key_remove is changed
|
||||
assert:
|
||||
that:
|
||||
- win_auto_logon_create_registry_key_remove is changed
|
||||
always:
|
||||
- name: make sure the auto logon is cleared
|
||||
win_auto_logon:
|
||||
state: absent
|
||||
|
||||
@ -0,0 +1,178 @@
|
||||
# Copyright: (c) 2019, Prasoon Karunan V (@prasoonkarunan) <kvprasoon@Live.in>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
---
|
||||
- name: set autologon registry keys (check mode)
|
||||
win_auto_logon:
|
||||
username: '{{ ansible_user }}'
|
||||
password: '{{ test_logon_password }}'
|
||||
state: present
|
||||
register: set_check
|
||||
check_mode: yes
|
||||
|
||||
- name: get acutal of set autologon registry keys (check mode)
|
||||
test_autologon_info:
|
||||
register: set_actual_check
|
||||
|
||||
- name: assert set autologon registry keys (check mode)
|
||||
assert:
|
||||
that:
|
||||
- set_check is changed
|
||||
- set_actual_check.AutoAdminLogon == 0
|
||||
- set_actual_check.AutoLogonCount == None
|
||||
- set_actual_check.DefaultDomainName == None
|
||||
- set_actual_check.DefaultPassword == 'abc'
|
||||
- set_actual_check.DefaultUserName == None
|
||||
- set_actual_check.LsaPassword == None
|
||||
|
||||
- name: set autologon registry keys
|
||||
win_auto_logon:
|
||||
username: '{{ ansible_user }}'
|
||||
password: '{{ test_logon_password }}'
|
||||
state: present
|
||||
register: set
|
||||
|
||||
- name: get acutal of set autologon registry keys
|
||||
test_autologon_info:
|
||||
register: set_actual
|
||||
|
||||
- name: assert set autologon registry keys
|
||||
assert:
|
||||
that:
|
||||
- set is changed
|
||||
- set_actual.AutoAdminLogon == 1
|
||||
- set_actual.AutoLogonCount == None
|
||||
- set_actual.DefaultDomainName == test_domain
|
||||
- set_actual.DefaultPassword == None
|
||||
- set_actual.DefaultUserName == test_user
|
||||
- set_actual.LsaPassword == test_logon_password
|
||||
|
||||
- name: set autologon registry keys (idempotent)
|
||||
win_auto_logon:
|
||||
username: '{{ ansible_user }}'
|
||||
password: '{{ test_logon_password }}'
|
||||
state: present
|
||||
register: set_again
|
||||
|
||||
- name: assert set autologon registry keys (idempotent)
|
||||
assert:
|
||||
that:
|
||||
- not set_again is changed
|
||||
|
||||
- name: add logon count (check mode)
|
||||
win_auto_logon:
|
||||
username: '{{ ansible_user }}'
|
||||
password: '{{ test_logon_password }}'
|
||||
logon_count: 2
|
||||
state: present
|
||||
register: logon_count_check
|
||||
check_mode: yes
|
||||
|
||||
- name: get result of add logon count (check mode)
|
||||
test_autologon_info:
|
||||
register: logon_count_actual_check
|
||||
|
||||
- name: assert add logon count (check mode)
|
||||
assert:
|
||||
that:
|
||||
- logon_count_check is changed
|
||||
- logon_count_actual_check.AutoLogonCount == None
|
||||
|
||||
- name: add logon count
|
||||
win_auto_logon:
|
||||
username: '{{ ansible_user }}'
|
||||
password: '{{ test_logon_password }}'
|
||||
logon_count: 2
|
||||
state: present
|
||||
register: logon_count
|
||||
|
||||
- name: get result of add logon count
|
||||
test_autologon_info:
|
||||
register: logon_count_actual
|
||||
|
||||
- name: assert add logon count
|
||||
assert:
|
||||
that:
|
||||
- logon_count is changed
|
||||
- logon_count_actual.AutoLogonCount == 2
|
||||
|
||||
- name: change auto logon (check mode)
|
||||
win_auto_logon:
|
||||
username: '{{ ansible_user }}'
|
||||
password: '{{ test_logon_password2 }}'
|
||||
state: present
|
||||
register: change_check
|
||||
check_mode: yes
|
||||
|
||||
- name: get reuslt of change auto logon (check mode)
|
||||
test_autologon_info:
|
||||
register: change_actual_check
|
||||
|
||||
- name: assert change auto logon (check mode)
|
||||
assert:
|
||||
that:
|
||||
- change_check is changed
|
||||
- change_actual_check == logon_count_actual
|
||||
|
||||
- name: change auto logon
|
||||
win_auto_logon:
|
||||
username: '{{ ansible_user }}'
|
||||
password: '{{ test_logon_password2 }}'
|
||||
state: present
|
||||
register: change
|
||||
|
||||
- name: get reuslt of change auto logon
|
||||
test_autologon_info:
|
||||
register: change_actual
|
||||
|
||||
- name: assert change auto logon
|
||||
assert:
|
||||
that:
|
||||
- change is changed
|
||||
- change_actual.AutoLogonCount == None
|
||||
- change_actual.LsaPassword == test_logon_password2
|
||||
|
||||
- name: remove autologon registry keys (check mode)
|
||||
win_auto_logon:
|
||||
state: absent
|
||||
register: remove_check
|
||||
check_mode: yes
|
||||
|
||||
- name: get result of remove autologon registry keys (check mode)
|
||||
test_autologon_info:
|
||||
register: remove_actual_check
|
||||
|
||||
- name: assert remove autologon registry keys (check mode)
|
||||
assert:
|
||||
that:
|
||||
- remove_check is changed
|
||||
- remove_actual_check == change_actual
|
||||
|
||||
- name: remove autologon registry keys
|
||||
win_auto_logon:
|
||||
state: absent
|
||||
register: remove
|
||||
|
||||
- name: get result of remove autologon registry keys
|
||||
test_autologon_info:
|
||||
register: remove_actual
|
||||
|
||||
- name: assert remove autologon registry keys
|
||||
assert:
|
||||
that:
|
||||
- remove is changed
|
||||
- remove_actual.AutoAdminLogon == 0
|
||||
- remove_actual.AutoLogonCount == None
|
||||
- remove_actual.DefaultDomainName == None
|
||||
- remove_actual.DefaultPassword == None
|
||||
- remove_actual.DefaultUserName == None
|
||||
- remove_actual.LsaPassword == None
|
||||
|
||||
- name: remove autologon registry keys (idempotent)
|
||||
win_auto_logon:
|
||||
state: absent
|
||||
register: remove_again
|
||||
|
||||
- name: assert remove autologon registry keys (idempotent)
|
||||
assert:
|
||||
that:
|
||||
- not remove_again is changed
|
||||
Loading…
Reference in New Issue