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
This commit is contained in:
parent
cff80f1319
commit
4d3ebd65db
6 changed files with 802 additions and 65 deletions
|
@ -3,14 +3,15 @@
|
|||
# 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"}
|
||||
username = @{type = "str"}
|
||||
|
@ -18,12 +19,15 @@ $spec = @{
|
|||
required_if = @(
|
||||
,@("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
|
||||
|
||||
$autoAdminLogon = 1
|
||||
# Make sure $null regardless of any input value if state: absent
|
||||
if ($state -eq 'absent') {
|
||||
$autoadminlogon = 0
|
||||
$password = $null
|
||||
}
|
||||
$autoLogonKeyList = @{
|
||||
DefaultPassword = $password
|
||||
DefaultUserName = $username
|
||||
DefaultDomain = $domain
|
||||
AutoAdminLogon = $autoAdminLogon
|
||||
}
|
||||
$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
|
||||
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;
|
||||
}
|
||||
elseif($state -eq 'absent') {
|
||||
$actionTaken = $true
|
||||
Remove-ItemProperty -LiteralPath $autoLogonRegPath -Name $key -Force
|
||||
|
||||
[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);
|
||||
}
|
||||
else {
|
||||
if ($state -eq 'present') {
|
||||
$actionTaken = $true
|
||||
New-ItemProperty -LiteralPath $autoLogonRegPath -Name $key -Value $autoLogonKeyList[$key] -Force | Out-Null
|
||||
catch
|
||||
{
|
||||
// Make sure we free the pointer before raising the exception.
|
||||
Marshal.FreeHGlobal(buffer);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
if($actionTaken){
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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 = @{
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
# 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 (-not $module.CheckMode) {
|
||||
Remove-ItemProperty -Name $key.Key @propParams
|
||||
}
|
||||
$module.Result.changed = $true
|
||||
}
|
||||
elseif ($state -eq 'absent' -and $null -ne $beforeVal) {
|
||||
if (-not $module.CheckMode) {
|
||||
Remove-ItemProperty -Name $key.Key @propParams
|
||||
}
|
||||
$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()
|
||||
|
||||
|
|
|
@ -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'''
|
||||
|
|
|
@ -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
|
||||
win_auto_logon:
|
||||
username: "{{ ansible_user }}"
|
||||
state: present
|
||||
register: win_auto_logon_create_registry_key_missing_input
|
||||
ignore_errors: true
|
||||
|
||||
- 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: Remove autologon registry keys
|
||||
- name: ensure auto logon is cleared before test
|
||||
win_auto_logon:
|
||||
state: absent
|
||||
register: win_auto_logon_create_registry_key_remove
|
||||
|
||||
- name: check win_auto_logon_create_registry_key_remove is changed
|
||||
assert:
|
||||
that:
|
||||
- win_auto_logon_create_registry_key_remove is changed
|
||||
- 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
|
||||
|
||||
- block:
|
||||
- name: run tests
|
||||
include_tasks: tests.yml
|
||||
|
||||
always:
|
||||
- name: make sure the auto logon is cleared
|
||||
win_auto_logon:
|
||||
state: absent
|
||||
|
|
178
test/integration/targets/win_auto_logon/tasks/tests.yml
Normal file
178
test/integration/targets/win_auto_logon/tasks/tests.yml
Normal file
|
@ -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 a new issue