mirror of
https://github.com/ansible-collections/community.general.git
synced 2025-04-24 19:31:26 -07:00
win become: refactor and add support for passwordless become (#48082)
* win become: refactor and add support for passwordless become * make tests more stable * fix up dep message for Load-CommandUtils * Add further check for System impersonation token * re-add support for become with accounts that have no password * doc fixes and slight code improvements * fix doc sanity issue
This commit is contained in:
parent
b3ac5b637a
commit
190d1ed7f1
13 changed files with 2586 additions and 1105 deletions
445
lib/ansible/module_utils/csharp/Ansible.Process.cs
Normal file
445
lib/ansible/module_utils/csharp/Ansible.Process.cs
Normal file
|
@ -0,0 +1,445 @@
|
|||
using Microsoft.Win32.SafeHandles;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.ConstrainedExecution;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ansible.Process
|
||||
{
|
||||
internal class NativeHelpers
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public class SECURITY_ATTRIBUTES
|
||||
{
|
||||
public UInt32 nLength;
|
||||
public IntPtr lpSecurityDescriptor;
|
||||
public bool bInheritHandle = false;
|
||||
public SECURITY_ATTRIBUTES()
|
||||
{
|
||||
nLength = (UInt32)Marshal.SizeOf(this);
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public class STARTUPINFO
|
||||
{
|
||||
public UInt32 cb;
|
||||
public IntPtr lpReserved;
|
||||
[MarshalAs(UnmanagedType.LPWStr)] public string lpDesktop;
|
||||
[MarshalAs(UnmanagedType.LPWStr)] public string lpTitle;
|
||||
public UInt32 dwX;
|
||||
public UInt32 dwY;
|
||||
public UInt32 dwXSize;
|
||||
public UInt32 dwYSize;
|
||||
public UInt32 dwXCountChars;
|
||||
public UInt32 dwYCountChars;
|
||||
public UInt32 dwFillAttribute;
|
||||
public StartupInfoFlags dwFlags;
|
||||
public UInt16 wShowWindow;
|
||||
public UInt16 cbReserved2;
|
||||
public IntPtr lpReserved2;
|
||||
public SafeFileHandle hStdInput;
|
||||
public SafeFileHandle hStdOutput;
|
||||
public SafeFileHandle hStdError;
|
||||
public STARTUPINFO()
|
||||
{
|
||||
cb = (UInt32)Marshal.SizeOf(this);
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public class STARTUPINFOEX
|
||||
{
|
||||
public STARTUPINFO startupInfo;
|
||||
public IntPtr lpAttributeList;
|
||||
public STARTUPINFOEX()
|
||||
{
|
||||
startupInfo = new STARTUPINFO();
|
||||
startupInfo.cb = (UInt32)Marshal.SizeOf(this);
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct PROCESS_INFORMATION
|
||||
{
|
||||
public IntPtr hProcess;
|
||||
public IntPtr hThread;
|
||||
public int dwProcessId;
|
||||
public int dwThreadId;
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum ProcessCreationFlags : uint
|
||||
{
|
||||
CREATE_NEW_CONSOLE = 0x00000010,
|
||||
CREATE_UNICODE_ENVIRONMENT = 0x00000400,
|
||||
EXTENDED_STARTUPINFO_PRESENT = 0x00080000
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum StartupInfoFlags : uint
|
||||
{
|
||||
USESTDHANDLES = 0x00000100
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum HandleFlags : uint
|
||||
{
|
||||
None = 0,
|
||||
INHERIT = 1
|
||||
}
|
||||
}
|
||||
|
||||
internal class NativeMethods
|
||||
{
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public static extern bool AllocConsole();
|
||||
|
||||
[DllImport("shell32.dll", SetLastError = true)]
|
||||
public static extern SafeMemoryBuffer CommandLineToArgvW(
|
||||
[MarshalAs(UnmanagedType.LPWStr)] string lpCmdLine,
|
||||
out int pNumArgs);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public static extern bool CreatePipe(
|
||||
out SafeFileHandle hReadPipe,
|
||||
out SafeFileHandle hWritePipe,
|
||||
NativeHelpers.SECURITY_ATTRIBUTES lpPipeAttributes,
|
||||
UInt32 nSize);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
public static extern bool CreateProcessW(
|
||||
[MarshalAs(UnmanagedType.LPWStr)] string lpApplicationName,
|
||||
StringBuilder lpCommandLine,
|
||||
IntPtr lpProcessAttributes,
|
||||
IntPtr lpThreadAttributes,
|
||||
bool bInheritHandles,
|
||||
NativeHelpers.ProcessCreationFlags dwCreationFlags,
|
||||
SafeMemoryBuffer lpEnvironment,
|
||||
[MarshalAs(UnmanagedType.LPWStr)] string lpCurrentDirectory,
|
||||
NativeHelpers.STARTUPINFOEX lpStartupInfo,
|
||||
out NativeHelpers.PROCESS_INFORMATION lpProcessInformation);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public static extern bool FreeConsole();
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public static extern IntPtr GetConsoleWindow();
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public static extern bool GetExitCodeProcess(
|
||||
SafeWaitHandle hProcess,
|
||||
out UInt32 lpExitCode);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
public static extern uint SearchPathW(
|
||||
[MarshalAs(UnmanagedType.LPWStr)] string lpPath,
|
||||
[MarshalAs(UnmanagedType.LPWStr)] string lpFileName,
|
||||
[MarshalAs(UnmanagedType.LPWStr)] string lpExtension,
|
||||
UInt32 nBufferLength,
|
||||
[MarshalAs(UnmanagedType.LPTStr)] StringBuilder lpBuffer,
|
||||
out IntPtr lpFilePart);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public static extern bool SetConsoleCP(
|
||||
UInt32 wCodePageID);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public static extern bool SetConsoleOutputCP(
|
||||
UInt32 wCodePageID);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public static extern bool SetHandleInformation(
|
||||
SafeFileHandle hObject,
|
||||
NativeHelpers.HandleFlags dwMask,
|
||||
NativeHelpers.HandleFlags dwFlags);
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
public static extern UInt32 WaitForSingleObject(
|
||||
SafeWaitHandle hHandle,
|
||||
UInt32 dwMilliseconds);
|
||||
}
|
||||
|
||||
internal class SafeMemoryBuffer : SafeHandleZeroOrMinusOneIsInvalid
|
||||
{
|
||||
public SafeMemoryBuffer() : base(true) { }
|
||||
public SafeMemoryBuffer(int cb) : base(true)
|
||||
{
|
||||
base.SetHandle(Marshal.AllocHGlobal(cb));
|
||||
}
|
||||
public SafeMemoryBuffer(IntPtr handle) : base(true)
|
||||
{
|
||||
base.SetHandle(handle);
|
||||
}
|
||||
|
||||
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
|
||||
protected override bool ReleaseHandle()
|
||||
{
|
||||
Marshal.FreeHGlobal(handle);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public class Win32Exception : System.ComponentModel.Win32Exception
|
||||
{
|
||||
private string _msg;
|
||||
|
||||
public Win32Exception(string message) : this(Marshal.GetLastWin32Error(), message) { }
|
||||
public Win32Exception(int errorCode, string message) : base(errorCode)
|
||||
{
|
||||
_msg = String.Format("{0} ({1}, Win32ErrorCode {2})", message, base.Message, errorCode);
|
||||
}
|
||||
|
||||
public override string Message { get { return _msg; } }
|
||||
public static explicit operator Win32Exception(string message) { return new Win32Exception(message); }
|
||||
}
|
||||
|
||||
public class Result
|
||||
{
|
||||
public string StandardOut { get; internal set; }
|
||||
public string StandardError { get; internal set; }
|
||||
public uint ExitCode { get; internal set; }
|
||||
}
|
||||
|
||||
public class ProcessUtil
|
||||
{
|
||||
/// <summary>
|
||||
/// Parses a command line string into an argv array according to the Windows rules
|
||||
/// </summary>
|
||||
/// <param name="lpCommandLine">The command line to parse</param>
|
||||
/// <returns>An array of arguments interpreted by Windows</returns>
|
||||
public static string[] ParseCommandLine(string lpCommandLine)
|
||||
{
|
||||
int numArgs;
|
||||
using (SafeMemoryBuffer buf = NativeMethods.CommandLineToArgvW(lpCommandLine, out numArgs))
|
||||
{
|
||||
if (buf.IsInvalid)
|
||||
throw new Win32Exception("Error parsing command line");
|
||||
IntPtr[] strptrs = new IntPtr[numArgs];
|
||||
Marshal.Copy(buf.DangerousGetHandle(), strptrs, 0, numArgs);
|
||||
return strptrs.Select(s => Marshal.PtrToStringUni(s)).ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Searches the path for the executable specified. Will throw a Win32Exception if the file is not found.
|
||||
/// </summary>
|
||||
/// <param name="lpFileName">The executable to search for</param>
|
||||
/// <returns>The full path of the executable to search for</returns>
|
||||
public static string SearchPath(string lpFileName)
|
||||
{
|
||||
StringBuilder sbOut = new StringBuilder(0);
|
||||
IntPtr filePartOut = IntPtr.Zero;
|
||||
UInt32 res = NativeMethods.SearchPathW(null, lpFileName, null, (UInt32)sbOut.Capacity, sbOut, out filePartOut);
|
||||
if (res == 0)
|
||||
{
|
||||
int lastErr = Marshal.GetLastWin32Error();
|
||||
if (lastErr == 2) // ERROR_FILE_NOT_FOUND
|
||||
throw new FileNotFoundException(String.Format("Could not find file '{0}'.", lpFileName));
|
||||
else
|
||||
throw new Win32Exception(String.Format("SearchPathW({0}) failed to get buffer length", lpFileName));
|
||||
}
|
||||
|
||||
sbOut.EnsureCapacity((int)res);
|
||||
if (NativeMethods.SearchPathW(null, lpFileName, null, (UInt32)sbOut.Capacity, sbOut, out filePartOut) == 0)
|
||||
throw new Win32Exception(String.Format("SearchPathW({0}) failed", lpFileName));
|
||||
|
||||
return sbOut.ToString();
|
||||
}
|
||||
|
||||
public static Result CreateProcess(string command)
|
||||
{
|
||||
return CreateProcess(null, command, null, null, String.Empty);
|
||||
}
|
||||
|
||||
public static Result CreateProcess(string lpApplicationName, string lpCommandLine, string lpCurrentDirectory,
|
||||
IDictionary environment)
|
||||
{
|
||||
return CreateProcess(lpApplicationName, lpCommandLine, lpCurrentDirectory, environment, String.Empty);
|
||||
}
|
||||
|
||||
public static Result CreateProcess(string lpApplicationName, string lpCommandLine, string lpCurrentDirectory,
|
||||
IDictionary environment, string stdin)
|
||||
{
|
||||
byte[] stdinBytes;
|
||||
if (String.IsNullOrEmpty(stdin))
|
||||
stdinBytes = new byte[0];
|
||||
else
|
||||
{
|
||||
if (!stdin.EndsWith(Environment.NewLine))
|
||||
stdin += Environment.NewLine;
|
||||
stdinBytes = new UTF8Encoding(false).GetBytes(stdin);
|
||||
}
|
||||
return CreateProcess(lpApplicationName, lpCommandLine, lpCurrentDirectory, environment, stdinBytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a process based on the CreateProcess API call.
|
||||
/// </summary>
|
||||
/// <param name="lpApplicationName">The name of the executable or batch file to execute</param>
|
||||
/// <param name="lpCommandLine">The command line to execute, typically this includes lpApplication as the first argument</param>
|
||||
/// <param name="lpCurrentDirectory">The full path to the current directory for the process, null will have the same cwd as the calling process</param>
|
||||
/// <param name="environment">A dictionary of key/value pairs to define the new process environment</param>
|
||||
/// <param name="stdin">A byte array to send over the stdin pipe</param>
|
||||
/// <returns>Result object that contains the command output and return code</returns>
|
||||
public static Result CreateProcess(string lpApplicationName, string lpCommandLine, string lpCurrentDirectory,
|
||||
IDictionary environment, byte[] stdin)
|
||||
{
|
||||
NativeHelpers.ProcessCreationFlags creationFlags = NativeHelpers.ProcessCreationFlags.CREATE_UNICODE_ENVIRONMENT |
|
||||
NativeHelpers.ProcessCreationFlags.EXTENDED_STARTUPINFO_PRESENT;
|
||||
NativeHelpers.PROCESS_INFORMATION pi = new NativeHelpers.PROCESS_INFORMATION();
|
||||
NativeHelpers.STARTUPINFOEX si = new NativeHelpers.STARTUPINFOEX();
|
||||
si.startupInfo.dwFlags = NativeHelpers.StartupInfoFlags.USESTDHANDLES;
|
||||
|
||||
SafeFileHandle stdoutRead, stdoutWrite, stderrRead, stderrWrite, stdinRead, stdinWrite;
|
||||
CreateStdioPipes(si, out stdoutRead, out stdoutWrite, out stderrRead, out stderrWrite, out stdinRead,
|
||||
out stdinWrite);
|
||||
FileStream stdinStream = new FileStream(stdinWrite, FileAccess.Write);
|
||||
|
||||
// $null from PowerShell ends up as an empty string, we need to convert back as an empty string doesn't
|
||||
// make sense for these parameters
|
||||
if (lpApplicationName == "")
|
||||
lpApplicationName = null;
|
||||
|
||||
if (lpCurrentDirectory == "")
|
||||
lpCurrentDirectory = null;
|
||||
|
||||
using (SafeMemoryBuffer lpEnvironment = CreateEnvironmentPointer(environment))
|
||||
{
|
||||
// Create console with utf-8 CP if no existing console is present
|
||||
bool isConsole = false;
|
||||
if (NativeMethods.GetConsoleWindow() == IntPtr.Zero)
|
||||
{
|
||||
isConsole = NativeMethods.AllocConsole();
|
||||
|
||||
// Set console input/output codepage to UTF-8
|
||||
NativeMethods.SetConsoleCP(65001);
|
||||
NativeMethods.SetConsoleOutputCP(65001);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
StringBuilder commandLine = new StringBuilder(lpCommandLine);
|
||||
if (!NativeMethods.CreateProcessW(lpApplicationName, commandLine, IntPtr.Zero, IntPtr.Zero,
|
||||
true, creationFlags, lpEnvironment, lpCurrentDirectory, si, out pi))
|
||||
{
|
||||
throw new Win32Exception("CreateProcessW() failed");
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (isConsole)
|
||||
NativeMethods.FreeConsole();
|
||||
}
|
||||
}
|
||||
|
||||
return WaitProcess(stdoutRead, stdoutWrite, stderrRead, stderrWrite, stdinStream, stdin, pi.hProcess);
|
||||
}
|
||||
|
||||
internal static void CreateStdioPipes(NativeHelpers.STARTUPINFOEX si, out SafeFileHandle stdoutRead,
|
||||
out SafeFileHandle stdoutWrite, out SafeFileHandle stderrRead, out SafeFileHandle stderrWrite,
|
||||
out SafeFileHandle stdinRead, out SafeFileHandle stdinWrite)
|
||||
{
|
||||
NativeHelpers.SECURITY_ATTRIBUTES pipesec = new NativeHelpers.SECURITY_ATTRIBUTES();
|
||||
pipesec.bInheritHandle = true;
|
||||
|
||||
if (!NativeMethods.CreatePipe(out stdoutRead, out stdoutWrite, pipesec, 0))
|
||||
throw new Win32Exception("STDOUT pipe setup failed");
|
||||
if (!NativeMethods.SetHandleInformation(stdoutRead, NativeHelpers.HandleFlags.INHERIT, 0))
|
||||
throw new Win32Exception("STDOUT pipe handle setup failed");
|
||||
|
||||
if (!NativeMethods.CreatePipe(out stderrRead, out stderrWrite, pipesec, 0))
|
||||
throw new Win32Exception("STDERR pipe setup failed");
|
||||
if (!NativeMethods.SetHandleInformation(stderrRead, NativeHelpers.HandleFlags.INHERIT, 0))
|
||||
throw new Win32Exception("STDERR pipe handle setup failed");
|
||||
|
||||
if (!NativeMethods.CreatePipe(out stdinRead, out stdinWrite, pipesec, 0))
|
||||
throw new Win32Exception("STDIN pipe setup failed");
|
||||
if (!NativeMethods.SetHandleInformation(stdinWrite, NativeHelpers.HandleFlags.INHERIT, 0))
|
||||
throw new Win32Exception("STDIN pipe handle setup failed");
|
||||
|
||||
si.startupInfo.hStdOutput = stdoutWrite;
|
||||
si.startupInfo.hStdError = stderrWrite;
|
||||
si.startupInfo.hStdInput = stdinRead;
|
||||
}
|
||||
|
||||
internal static SafeMemoryBuffer CreateEnvironmentPointer(IDictionary environment)
|
||||
{
|
||||
IntPtr lpEnvironment = IntPtr.Zero;
|
||||
if (environment != null && environment.Count > 0)
|
||||
{
|
||||
StringBuilder environmentString = new StringBuilder();
|
||||
foreach (DictionaryEntry kv in environment)
|
||||
environmentString.AppendFormat("{0}={1}\0", kv.Key, kv.Value);
|
||||
environmentString.Append('\0');
|
||||
|
||||
lpEnvironment = Marshal.StringToHGlobalUni(environmentString.ToString());
|
||||
}
|
||||
return new SafeMemoryBuffer(lpEnvironment);
|
||||
}
|
||||
|
||||
internal static Result WaitProcess(SafeFileHandle stdoutRead, SafeFileHandle stdoutWrite, SafeFileHandle stderrRead,
|
||||
SafeFileHandle stderrWrite, FileStream stdinStream, byte[] stdin, IntPtr hProcess)
|
||||
{
|
||||
// Setup the output buffers and get stdout/stderr
|
||||
UTF8Encoding utf8Encoding = new UTF8Encoding(false);
|
||||
FileStream stdoutFS = new FileStream(stdoutRead, FileAccess.Read, 4096);
|
||||
StreamReader stdout = new StreamReader(stdoutFS, utf8Encoding, true, 4096);
|
||||
stdoutWrite.Close();
|
||||
|
||||
FileStream stderrFS = new FileStream(stderrRead, FileAccess.Read, 4096);
|
||||
StreamReader stderr = new StreamReader(stderrFS, utf8Encoding, true, 4096);
|
||||
stderrWrite.Close();
|
||||
|
||||
stdinStream.Write(stdin, 0, stdin.Length);
|
||||
stdinStream.Close();
|
||||
|
||||
string stdoutStr, stderrStr = null;
|
||||
GetProcessOutput(stdout, stderr, out stdoutStr, out stderrStr);
|
||||
UInt32 rc = GetProcessExitCode(hProcess);
|
||||
|
||||
return new Result
|
||||
{
|
||||
StandardOut = stdoutStr,
|
||||
StandardError = stderrStr,
|
||||
ExitCode = rc
|
||||
};
|
||||
}
|
||||
|
||||
internal static void GetProcessOutput(StreamReader stdoutStream, StreamReader stderrStream, out string stdout, out string stderr)
|
||||
{
|
||||
var sowait = new EventWaitHandle(false, EventResetMode.ManualReset);
|
||||
var sewait = new EventWaitHandle(false, EventResetMode.ManualReset);
|
||||
string so = null, se = null;
|
||||
ThreadPool.QueueUserWorkItem((s) =>
|
||||
{
|
||||
so = stdoutStream.ReadToEnd();
|
||||
sowait.Set();
|
||||
});
|
||||
ThreadPool.QueueUserWorkItem((s) =>
|
||||
{
|
||||
se = stderrStream.ReadToEnd();
|
||||
sewait.Set();
|
||||
});
|
||||
foreach (var wh in new WaitHandle[] { sowait, sewait })
|
||||
wh.WaitOne();
|
||||
stdout = so;
|
||||
stderr = se;
|
||||
}
|
||||
|
||||
internal static UInt32 GetProcessExitCode(IntPtr processHandle)
|
||||
{
|
||||
SafeWaitHandle hProcess = new SafeWaitHandle(processHandle, true);
|
||||
NativeMethods.WaitForSingleObject(hProcess, 0xFFFFFFFF);
|
||||
|
||||
UInt32 exitCode;
|
||||
if (!NativeMethods.GetExitCodeProcess(hProcess, out exitCode))
|
||||
throw new Win32Exception("GetExitCodeProcess() failed");
|
||||
return exitCode;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue