summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJordan Borean <jborean93@gmail.com>2019-12-05 02:24:30 +0100
committerGitHub <noreply@github.com>2019-12-05 02:24:30 +0100
commit4d3ebd65db512b445e1de4a62dfa9fb1fa3f6156 (patch)
treee4ba7cf786db1f4aac4cc5fd1ebb2bc0840604d1
parentazure_rm_galleryimageversion: support data disk snapshot as source input (#65... (diff)
downloadansible-4d3ebd65db512b445e1de4a62dfa9fb1fa3f6156.tar.xz
ansible-4d3ebd65db512b445e1de4a62dfa9fb1fa3f6156.zip
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 cleared
-rw-r--r--lib/ansible/modules/windows/win_auto_logon.ps1388
-rw-r--r--lib/ansible/modules/windows/win_auto_logon.py16
-rw-r--r--test/integration/targets/win_auto_logon/defaults/main.yml3
-rw-r--r--test/integration/targets/win_auto_logon/library/test_autologon_info.ps1214
-rw-r--r--test/integration/targets/win_auto_logon/tasks/main.yml64
-rw-r--r--test/integration/targets/win_auto_logon/tasks/tests.yml178
6 files changed, 800 insertions, 63 deletions
diff --git a/lib/ansible/modules/windows/win_auto_logon.ps1 b/lib/ansible/modules/windows/win_auto_logon.ps1
index 25d3bdfbfb..61b0d67f5a 100644
--- a/lib/ansible/modules/windows/win_auto_logon.ps1
+++ b/lib/ansible/modules/windows/win_auto_logon.ps1
@@ -3,27 +3,31 @@
# 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)
-
# All helper methods are written in a binary module and has to be loaded for consuming them.
#AnsibleRequires -CSharpUtil Ansible.Basic
+#Requires -Module Ansible.ModuleUtils.AddType
Set-StrictMode -Version 2.0
$spec = @{
options = @{
+ logon_count = @{type = "int"}
password = @{type = "str"; no_log = $true}
- state = @{type = "str"; choices = "absent","present"; default = "present"}
+ state = @{type = "str"; choices = "absent", "present"; default = "present"}
username = @{type = "str"}
}
required_if = @(
- , @("state", "present", @("username", "password"))
+ ,@("state", "present", @("username", "password"))
)
+ supports_check_mode = $true
}
$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec)
-$password = $module.params.password
-$state = $module.params.state
-$username = $module.params.username
+
+$logonCount = $module.Params.logon_count
+$password = $module.Params.password
+$state = $module.Params.state
+$username = $module.Params.username
$domain = $null
if ($username) {
@@ -40,44 +44,360 @@ if ($username) {
$domain, $username = $ntAccount.Value -split '\\'
}
-#Build ParamHash
+# Make sure $null regardless of any input value if state: absent
+if ($state -eq 'absent') {
+ $password = $null
+}
+
+Add-CSharpType -AnsibleModule $module -References @'
+using Microsoft.Win32.SafeHandles;
+using System;
+using System.Runtime.ConstrainedExecution;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace Ansible.WinAutoLogon
+{
+ 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,
+ LsaPolicyAccessMask AccessMask,
+ out SafeLsaHandle PolicyHandle);
+
+ [DllImport("Advapi32.dll")]
+ public static extern UInt32 LsaRetrievePrivateData(
+ SafeLsaHandle PolicyHandle,
+ SafeMemoryBuffer KeyName,
+ out SafeLsaMemory PrivateData);
+
+ [DllImport("Advapi32.dll")]
+ public static extern UInt32 LsaStorePrivateData(
+ SafeLsaHandle PolicyHandle,
+ SafeMemoryBuffer KeyName,
+ SafeMemoryBuffer 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); }
+ }
+
+ [Flags]
+ public enum LsaPolicyAccessMask : uint
+ {
+ ViewLocalInformation = 0x00000001,
+ ViewAuditInformation = 0x00000002,
+ GetPrivateInformation = 0x00000004,
+ TrustAdmin = 0x00000008,
+ CreateAccount = 0x00000010,
+ CreateSecret = 0x00000020,
+ CreatePrivilege = 0x00000040,
+ SetDefaultQuotaLimits = 0x00000080,
+ SetAuditRequirements = 0x00000100,
+ AuditLogAdmin = 0x00000200,
+ ServerAdmin = 0x00000400,
+ LookupNames = 0x00000800,
+ Read = 0x00020006,
+ Write = 0x000207F8,
+ Execute = 0x00020801,
+ AllAccess = 0x000F0FFF,
+ }
+
+ public class LsaUtil
+ {
+ public static SafeLsaHandle OpenPolicy(LsaPolicyAccessMask 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;
+ }
-$autoAdminLogon = 1
-if($state -eq 'absent'){
- $autoadminlogon = 0
+ 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;
+ }
+ }
+ }
+
+ public static void StorePrivateData(SafeLsaHandle handle, string key, string data)
+ {
+ using (SafeMemoryBuffer keyBuffer = NativeHelpers.LSA_UNICODE_STRING.CreateSafeBuffer(key))
+ using (SafeMemoryBuffer dataBuffer = NativeHelpers.LSA_UNICODE_STRING.CreateSafeBuffer(data))
+ {
+ UInt32 res = NativeMethods.LsaStorePrivateData(handle, keyBuffer, dataBuffer);
+ if (res != 0)
+ {
+ // When clearing the private data with null it may return this error which we can ignore.
+ if (data == null && res == 0xC0000034) // STATUS_OBJECT_NAME_NOT_FOUND
+ return;
+
+ throw new Win32Exception(NativeMethods.LsaNtStatusToWinError(res),
+ String.Format("LsaStorePrivateData({0}) failed", key));
+ }
+ }
+ }
+ }
+}
+'@
+
+$autoLogonRegPath = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon'
+$logonDetails = Get-ItemProperty -LiteralPath $autoLogonRegPath
+
+$before = @{
+ state = 'absent'
+}
+if ('AutoAdminLogon' -in $logonDetails.PSObject.Properties.Name -and $logonDetails.AutoAdminLogon -eq 1) {
+ $before.state = 'present'
+}
+
+$mapping = @{
+ DefaultUserName = 'username'
+ DefaultDomainName = 'domain'
+ AutoLogonCount = 'logon_count'
+}
+foreach ($map_detail in $mapping.GetEnumerator()) {
+ if ($map_detail.Key -in $logonDetails.PSObject.Properties.Name) {
+ $before."$($map_detail.Value)" = $logonDetails."$($map_detail.Key)"
+ }
+}
+
+$module.Diff.before = $before
+
+$propParams = @{
+ LiteralPath = $autoLogonRegPath
+ WhatIf = $module.CheckMode
+ Force = $true
+}
+
+# First set the registry information
+# The DefaultPassword reg key should never be set, we use LSA to store the password in a more secure way.
+if ('DefaultPassword' -in (Get-Item -LiteralPath $autoLogonRegPath).Property) {
+ # Bug on older Windows hosts where -WhatIf causes it fail to find the property
+ if (-not $module.CheckMode) {
+ Remove-ItemProperty -Name 'DefaultPassword' @propParams
+ }
+ $module.Result.changed = $true
}
-$autoLogonKeyList = @{
- DefaultPassword = $password
- DefaultUserName = $username
- DefaultDomain = $domain
- AutoAdminLogon = $autoAdminLogon
+
+$autoLogonKeyList = @{
+ DefaultUserName = @{
+ before = if ($before.ContainsKey('username')) { $before.username } else { $null }
+ after = $username
+ }
+ DefaultDomainName = @{
+ before = if ($before.ContainsKey('domain')) { $before.domain } else { $null }
+ after = $domain
+ }
+ AutoLogonCount = @{
+ before = if ($before.ContainsKey('logon_count')) { $before.logon_count } else { $null }
+ after = $logonCount
+ }
}
-$actionTaken = $null
-$autoLogonRegPath = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\'
-$autoLogonKeyRegList = Get-ItemProperty -LiteralPath $autoLogonRegPath -Name $autoLogonKeyList.GetEnumerator().Name -ErrorAction SilentlyContinue
-Foreach($key in $autoLogonKeyList.GetEnumerator().Name){
- $currentKeyValue = $autoLogonKeyRegList | Select-Object -ExpandProperty $key -ErrorAction SilentlyContinue
- if (-not [String]::IsNullOrEmpty($currentKeyValue)) {
- $expectedValue = $autoLogonKeyList[$key]
- if(($state -eq 'present') -and ($currentKeyValue -ne $expectedValue)) {
- Set-ItemProperty -LiteralPath $autoLogonRegPath -Name $key -Value $autoLogonKeyList[$key] -Force
- $actionTaken = $true
+# Check AutoAdminLogon separately as it has different logic (key must exist)
+if ($state -ne $before.state) {
+ $newValue = if ($state -eq 'present') { 1 } else { 0 }
+ $null = New-ItemProperty -Name 'AutoAdminLogon' -Value $newValue -PropertyType DWord @propParams
+ $module.Result.changed = $true
+}
+
+foreach ($key in $autoLogonKeyList.GetEnumerator()) {
+ $beforeVal = $key.Value.before
+ $after = $key.Value.after
+
+ if ($state -eq 'present' -and $beforeVal -cne $after) {
+ if ($null -ne $after) {
+ $null = New-ItemProperty -Name $key.Key -Value $after @propParams
}
- elseif($state -eq 'absent') {
- $actionTaken = $true
- Remove-ItemProperty -LiteralPath $autoLogonRegPath -Name $key -Force
+ elseif (-not $module.CheckMode) {
+ Remove-ItemProperty -Name $key.Key @propParams
}
+ $module.Result.changed = $true
}
- else {
- if ($state -eq 'present') {
- $actionTaken = $true
- New-ItemProperty -LiteralPath $autoLogonRegPath -Name $key -Value $autoLogonKeyList[$key] -Force | Out-Null
+ elseif ($state -eq 'absent' -and $null -ne $beforeVal) {
+ if (-not $module.CheckMode) {
+ Remove-ItemProperty -Name $key.Key @propParams
}
+ $module.Result.changed = $true
}
}
-if($actionTaken){
- $module.Result.changed = $true
+
+# Finally update the password in the LSA private store.
+$lsaHandle = [Ansible.WinAutoLogon.LsaUtil]::OpenPolicy('CreateSecret, GetPrivateInformation')
+try {
+ $beforePass = [Ansible.WinAutoLogon.LsaUtil]::RetrievePrivateData($lsaHandle, 'DefaultPassword')
+
+ if ($beforePass -cne $password) {
+ # Due to .NET marshaling we need to pass in $null as NullString.Value so it's truly a null value.
+ if ($null -eq $password) {
+ $password = [NullString]::Value
+ }
+ if (-not $module.CheckMode) {
+ [Ansible.WinAutoLogon.LsaUtil]::StorePrivateData($lsaHandle, 'DefaultPassword', $password)
+ }
+ $module.Result.changed = $true
+ }
+}
+finally {
+ $lsaHandle.Dispose()
+}
+
+# Need to manually craft the after diff in case we are running in check mode
+$module.Diff.after = @{
+ state = $state
+}
+if ($state -eq 'present') {
+ $module.Diff.after.username = $username
+ $module.Diff.after.domain = $domain
+ if ($null -ne $logonCount) {
+ $module.Diff.after.logon_count = $logonCount
+ }
}
$module.ExitJson()
+
diff --git a/lib/ansible/modules/windows/win_auto_logon.py b/lib/ansible/modules/windows/win_auto_logon.py
index fe31b2961f..c869361df9 100644
--- a/lib/ansible/modules/windows/win_auto_logon.py
+++ b/lib/ansible/modules/windows/win_auto_logon.py
@@ -16,6 +16,14 @@ description:
- Used to apply auto logon registry setting.
version_added: "2.10"
options:
+ logon_count:
+ description:
+ - The number of times to do an automatic logon.
+ - This count is deremented by Windows everytime an automatic logon is
+ performed.
+ - Once the count reaches C(0) then the automatic logon process is
+ disabled.
+ type: int
username:
description:
- Username to login automatically.
@@ -29,6 +37,8 @@ options:
- Password to be used for automatic login.
- Must be set when C(state=present).
- Value of this input will be used as password for I(username).
+ - While this value is encrypted by LSA it is decryptable to any user who
+ is an Administrator on the remote host.
type: str
state:
description:
@@ -54,6 +64,12 @@ EXAMPLES = r'''
- name: Remove autologon for user1
win_auto_logon:
state: absent
+
+- name: Set autologon for user1 with a limited logon count
+ win_auto_logon:
+ username: User1
+ password: str0ngp@ssword
+ logon_count: 5
'''
RETURN = r'''
diff --git a/test/integration/targets/win_auto_logon/defaults/main.yml b/test/integration/targets/win_auto_logon/defaults/main.yml
new file mode 100644
index 0000000000..d5462bb6a3
--- /dev/null
+++ b/test/integration/targets/win_auto_logon/defaults/main.yml
@@ -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(;)]'
diff --git a/test/integration/targets/win_auto_logon/library/test_autologon_info.ps1 b/test/integration/targets/win_auto_logon/library/test_autologon_info.ps1
new file mode 100644
index 0000000000..ef4cf2d0f3
--- /dev/null
+++ b/test/integration/targets/win_auto_logon/library/test_autologon_info.ps1
@@ -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()
diff --git a/test/integration/targets/win_auto_logon/tasks/main.yml b/test/integration/targets/win_auto_logon/tasks/main.yml
index 0636d7128f..f8a8d01624 100644
--- a/test/integration/targets/win_auto_logon/tasks/main.yml
+++ b/test/integration/targets/win_auto_logon/tasks/main.yml
@@ -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
diff --git a/test/integration/targets/win_auto_logon/tasks/tests.yml b/test/integration/targets/win_auto_logon/tasks/tests.yml
new file mode 100644
index 0000000000..c25e07709b
--- /dev/null
+++ b/test/integration/targets/win_auto_logon/tasks/tests.yml
@@ -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