mirror of
https://github.com/valitydev/salt.git
synced 2024-11-06 16:45:27 +00:00
Merge pull request #47621 from dwoz/win_runas
Runas any user even when shell is limited like winrm
This commit is contained in:
commit
a9daa92d73
@ -895,3 +895,15 @@ particular Salt job. The JID will be included using the default format,
|
||||
.. code-block:: yaml
|
||||
|
||||
log_fmt_jid: "[JID: %(jid)s]"
|
||||
|
||||
Security
|
||||
========
|
||||
|
||||
Windows runas changes
|
||||
---------------------
|
||||
|
||||
A password is no longer required with ``runas`` under normal circumstances.
|
||||
The password option is only needed if the minion process is run under a
|
||||
restricted (non-administrator) account. In the aforementioned case, a password
|
||||
is only required when using the ``runas`` argument to run command as a
|
||||
different user.
|
||||
|
@ -397,10 +397,6 @@ def _run(cmd,
|
||||
log.info(log_callback(msg))
|
||||
|
||||
if runas and salt.utils.platform.is_windows():
|
||||
if not password:
|
||||
msg = 'password is a required argument for runas on Windows'
|
||||
raise CommandExecutionError(msg)
|
||||
|
||||
if not HAS_WIN_RUNAS:
|
||||
msg = 'missing salt/utils/win_runas.py'
|
||||
raise CommandExecutionError(msg)
|
||||
@ -966,15 +962,14 @@ def run(cmd,
|
||||
cases where sensitive information must be read from standard input.
|
||||
|
||||
:param str runas: Specify an alternate user to run the command. The default
|
||||
behavior is to run as the user under which Salt is running. If running
|
||||
on a Windows minion you must also use the ``password`` argument, and
|
||||
the target user account must be in the Administrators group.
|
||||
behavior is to run as the user under which Salt is running.
|
||||
|
||||
:param str group: Group to run command as. Not currently supported
|
||||
on Windows.
|
||||
on Windows.
|
||||
|
||||
:param str password: Windows only. Required when specifying ``runas``. This
|
||||
parameter will be ignored on non-Windows platforms.
|
||||
:param str password: Windows only. Only required when the minion proccess
|
||||
is running under a non-privileged account. This parameter will be
|
||||
ignored on non-Windows platforms.
|
||||
|
||||
.. versionadded:: 2016.3.0
|
||||
|
||||
|
1
salt/platform/__init__.py
Normal file
1
salt/platform/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
# -*- coding: utf-8 -*-
|
1196
salt/platform/win.py
Normal file
1196
salt/platform/win.py
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,34 +1,35 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
'''
|
||||
Run processes as a different user in Windows
|
||||
|
||||
Based on a solution from http://stackoverflow.com/questions/29566330
|
||||
'''
|
||||
from __future__ import absolute_import, print_function, unicode_literals
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
# Import Python Libraries
|
||||
import ctypes
|
||||
import os
|
||||
import logging
|
||||
|
||||
# Import Third Party Libs
|
||||
try:
|
||||
import win32con
|
||||
import psutil
|
||||
HAS_PSUTIL = True
|
||||
except ImportError:
|
||||
HAS_PSUTIL = False
|
||||
|
||||
try:
|
||||
import win32api
|
||||
import win32con
|
||||
import win32process
|
||||
import win32security
|
||||
import win32pipe
|
||||
import win32event
|
||||
import win32profile
|
||||
import msvcrt
|
||||
import ctypes
|
||||
import winerror
|
||||
import salt.utils.win_functions
|
||||
from ctypes import wintypes
|
||||
import salt.platform.win
|
||||
HAS_WIN32 = True
|
||||
except ImportError:
|
||||
HAS_WIN32 = False
|
||||
|
||||
# Set up logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@ -38,405 +39,231 @@ def __virtual__():
|
||||
'''
|
||||
Only load if Win32 Libraries are installed
|
||||
'''
|
||||
if not HAS_WIN32:
|
||||
return False, 'This utility requires pywin32'
|
||||
if not HAS_WIN32 or not HAS_PSUTIL:
|
||||
return False, 'This utility requires pywin32 and psutil'
|
||||
|
||||
return 'win_runas'
|
||||
|
||||
|
||||
if HAS_WIN32:
|
||||
# ctypes definitions
|
||||
kernel32 = ctypes.WinDLL(str('kernel32')) # future lint: disable=blacklisted-function
|
||||
advapi32 = ctypes.WinDLL(str('advapi32')) # future lint: disable=blacklisted-function
|
||||
|
||||
INVALID_HANDLE_VALUE = wintypes.HANDLE(-1).value
|
||||
INVALID_DWORD_VALUE = wintypes.DWORD(-1).value # ~WinAPI
|
||||
INFINITE = INVALID_DWORD_VALUE
|
||||
|
||||
LOGON_WITH_PROFILE = 0x00000001
|
||||
|
||||
STD_INPUT_HANDLE = wintypes.DWORD(-10).value
|
||||
STD_OUTPUT_HANDLE = wintypes.DWORD(-11).value
|
||||
STD_ERROR_HANDLE = wintypes.DWORD(-12).value
|
||||
|
||||
class SECURITY_ATTRIBUTES(ctypes.Structure):
|
||||
_fields_ = (('nLength', wintypes.DWORD),
|
||||
('lpSecurityDescriptor', wintypes.LPVOID),
|
||||
('bInheritHandle', wintypes.BOOL))
|
||||
|
||||
def __init__(self, **kwds):
|
||||
self.nLength = ctypes.sizeof(self)
|
||||
super(SECURITY_ATTRIBUTES, self).__init__(**kwds)
|
||||
|
||||
LPSECURITY_ATTRIBUTES = ctypes.POINTER(SECURITY_ATTRIBUTES)
|
||||
LPBYTE = ctypes.POINTER(wintypes.BYTE)
|
||||
LPHANDLE = PHANDLE = ctypes.POINTER(ctypes.c_void_p)
|
||||
LPDWORD = ctypes.POINTER(ctypes.c_ulong)
|
||||
|
||||
class STARTUPINFO(ctypes.Structure):
|
||||
"""https://msdn.microsoft.com/en-us/library/ms686331"""
|
||||
_fields_ = (('cb', wintypes.DWORD),
|
||||
('lpReserved', wintypes.LPWSTR),
|
||||
('lpDesktop', wintypes.LPWSTR),
|
||||
('lpTitle', wintypes.LPWSTR),
|
||||
('dwX', wintypes.DWORD),
|
||||
('dwY', wintypes.DWORD),
|
||||
('dwXSize', wintypes.DWORD),
|
||||
('dwYSize', wintypes.DWORD),
|
||||
('dwXCountChars', wintypes.DWORD),
|
||||
('dwYCountChars', wintypes.DWORD),
|
||||
('dwFillAttribute', wintypes.DWORD),
|
||||
('dwFlags', wintypes.DWORD),
|
||||
('wShowWindow', wintypes.WORD),
|
||||
('cbReserved2', wintypes.WORD),
|
||||
('lpReserved2', LPBYTE),
|
||||
('hStdInput', wintypes.HANDLE),
|
||||
('hStdOutput', wintypes.HANDLE),
|
||||
('hStdError', wintypes.HANDLE))
|
||||
|
||||
def __init__(self, **kwds):
|
||||
self.cb = ctypes.sizeof(self)
|
||||
super(STARTUPINFO, self).__init__(**kwds)
|
||||
|
||||
if HAS_WIN32:
|
||||
LPSTARTUPINFO = ctypes.POINTER(STARTUPINFO)
|
||||
|
||||
class PROC_THREAD_ATTRIBUTE_LIST(ctypes.Structure):
|
||||
pass
|
||||
|
||||
PPROC_THREAD_ATTRIBUTE_LIST = ctypes.POINTER(PROC_THREAD_ATTRIBUTE_LIST)
|
||||
|
||||
class STARTUPINFOEX(STARTUPINFO):
|
||||
_fields_ = (('lpAttributeList', PPROC_THREAD_ATTRIBUTE_LIST),)
|
||||
|
||||
LPSTARTUPINFOEX = ctypes.POINTER(STARTUPINFOEX)
|
||||
|
||||
class PROCESS_INFORMATION(ctypes.Structure):
|
||||
"""https://msdn.microsoft.com/en-us/library/ms684873"""
|
||||
_fields_ = (('hProcess', wintypes.HANDLE),
|
||||
('hThread', wintypes.HANDLE),
|
||||
('dwProcessId', wintypes.DWORD),
|
||||
('dwThreadId', wintypes.DWORD))
|
||||
|
||||
LPPROCESS_INFORMATION = ctypes.POINTER(PROCESS_INFORMATION)
|
||||
|
||||
class HANDLE_IHV(wintypes.HANDLE):
|
||||
pass
|
||||
|
||||
def errcheck_ihv(result, func, args):
|
||||
if result.value == INVALID_HANDLE_VALUE:
|
||||
raise ctypes.WinError()
|
||||
return result.value
|
||||
|
||||
class DWORD_IDV(wintypes.DWORD):
|
||||
pass
|
||||
|
||||
def errcheck_idv(result, func, args):
|
||||
if result.value == INVALID_DWORD_VALUE:
|
||||
raise ctypes.WinError()
|
||||
return result.value
|
||||
|
||||
def errcheck_bool(result, func, args):
|
||||
if not result:
|
||||
raise ctypes.WinError()
|
||||
return args
|
||||
|
||||
def _win(func, restype, *argtypes):
|
||||
func.restype = restype
|
||||
func.argtypes = argtypes
|
||||
if issubclass(restype, HANDLE_IHV):
|
||||
func.errcheck = errcheck_ihv
|
||||
elif issubclass(restype, DWORD_IDV):
|
||||
func.errcheck = errcheck_idv
|
||||
else:
|
||||
func.errcheck = errcheck_bool
|
||||
|
||||
# https://msdn.microsoft.com/en-us/library/ms687032
|
||||
_win(kernel32.WaitForSingleObject, DWORD_IDV,
|
||||
wintypes.HANDLE, # _In_ hHandle
|
||||
wintypes.DWORD) # _In_ dwMilliseconds
|
||||
|
||||
# https://msdn.microsoft.com/en-us/library/ms683231
|
||||
_win(kernel32.GetStdHandle, HANDLE_IHV,
|
||||
wintypes.DWORD) # _In_ nStdHandle
|
||||
|
||||
# https://msdn.microsoft.com/en-us/library/ms724211
|
||||
_win(kernel32.CloseHandle, wintypes.BOOL,
|
||||
wintypes.HANDLE) # _In_ hObject
|
||||
|
||||
# https://msdn.microsoft.com/en-us/library/ms724935
|
||||
_win(kernel32.SetHandleInformation, wintypes.BOOL,
|
||||
wintypes.HANDLE, # _In_ hObject
|
||||
wintypes.DWORD, # _In_ dwMask
|
||||
wintypes.DWORD) # _In_ dwFlags
|
||||
|
||||
# https://msdn.microsoft.com/en-us/library/ms724251
|
||||
_win(kernel32.DuplicateHandle, wintypes.BOOL,
|
||||
wintypes.HANDLE, # _In_ hSourceProcessHandle,
|
||||
wintypes.HANDLE, # _In_ hSourceHandle,
|
||||
wintypes.HANDLE, # _In_ hTargetProcessHandle,
|
||||
LPHANDLE, # _Out_ lpTargetHandle,
|
||||
wintypes.DWORD, # _In_ dwDesiredAccess,
|
||||
wintypes.BOOL, # _In_ bInheritHandle,
|
||||
wintypes.DWORD) # _In_ dwOptions
|
||||
|
||||
# https://msdn.microsoft.com/en-us/library/ms683179
|
||||
_win(kernel32.GetCurrentProcess, wintypes.HANDLE)
|
||||
|
||||
# https://msdn.microsoft.com/en-us/library/ms683189
|
||||
_win(kernel32.GetExitCodeProcess, wintypes.BOOL,
|
||||
wintypes.HANDLE, # _In_ hProcess,
|
||||
LPDWORD) # _Out_ lpExitCode
|
||||
|
||||
# https://msdn.microsoft.com/en-us/library/aa365152
|
||||
_win(kernel32.CreatePipe, wintypes.BOOL,
|
||||
PHANDLE, # _Out_ hReadPipe,
|
||||
PHANDLE, # _Out_ hWritePipe,
|
||||
LPSECURITY_ATTRIBUTES, # _In_opt_ lpPipeAttributes,
|
||||
wintypes.DWORD) # _In_ nSize
|
||||
|
||||
# https://msdn.microsoft.com/en-us/library/ms682431
|
||||
_win(advapi32.CreateProcessWithLogonW, wintypes.BOOL,
|
||||
wintypes.LPCWSTR, # _In_ lpUsername
|
||||
wintypes.LPCWSTR, # _In_opt_ lpDomain
|
||||
wintypes.LPCWSTR, # _In_ lpPassword
|
||||
wintypes.DWORD, # _In_ dwLogonFlags
|
||||
wintypes.LPCWSTR, # _In_opt_ lpApplicationName
|
||||
wintypes.LPWSTR, # _Inout_opt_ lpCommandLine
|
||||
wintypes.DWORD, # _In_ dwCreationFlags
|
||||
wintypes.LPCWSTR, # _In_opt_ lpEnvironment
|
||||
wintypes.LPCWSTR, # _In_opt_ lpCurrentDirectory
|
||||
LPSTARTUPINFO, # _In_ lpStartupInfo
|
||||
LPPROCESS_INFORMATION) # _Out_ lpProcessInformation
|
||||
|
||||
# High-level wrappers
|
||||
def DuplicateHandle(hsrc=kernel32.GetCurrentProcess(),
|
||||
srchandle=kernel32.GetCurrentProcess(),
|
||||
htgt=kernel32.GetCurrentProcess(),
|
||||
access=0, inherit=False,
|
||||
options=win32con.DUPLICATE_SAME_ACCESS):
|
||||
tgthandle = wintypes.HANDLE()
|
||||
kernel32.DuplicateHandle(hsrc, srchandle,
|
||||
htgt, ctypes.byref(tgthandle),
|
||||
access, inherit, options)
|
||||
return tgthandle.value
|
||||
|
||||
def CreatePipe(inherit_read=False, inherit_write=False):
|
||||
read, write = wintypes.HANDLE(), wintypes.HANDLE()
|
||||
kernel32.CreatePipe(ctypes.byref(read), ctypes.byref(write), None, 0)
|
||||
if inherit_read:
|
||||
kernel32.SetHandleInformation(read, win32con.HANDLE_FLAG_INHERIT,
|
||||
win32con.HANDLE_FLAG_INHERIT)
|
||||
if inherit_write:
|
||||
kernel32.SetHandleInformation(write, win32con.HANDLE_FLAG_INHERIT,
|
||||
win32con.HANDLE_FLAG_INHERIT)
|
||||
return read.value, write.value
|
||||
|
||||
def CreateProcessWithLogonW(username=None,
|
||||
domain=None,
|
||||
password=None,
|
||||
logonflags=0,
|
||||
applicationname=None,
|
||||
commandline=None,
|
||||
creationflags=0,
|
||||
environment=None,
|
||||
currentdirectory=None,
|
||||
startupinfo=None):
|
||||
creationflags |= win32con.CREATE_UNICODE_ENVIRONMENT
|
||||
if commandline is not None:
|
||||
commandline = ctypes.create_unicode_buffer(commandline)
|
||||
if startupinfo is None:
|
||||
startupinfo = STARTUPINFO()
|
||||
process_info = PROCESS_INFORMATION()
|
||||
advapi32.CreateProcessWithLogonW(username,
|
||||
domain,
|
||||
password,
|
||||
logonflags,
|
||||
applicationname,
|
||||
commandline,
|
||||
creationflags,
|
||||
environment,
|
||||
currentdirectory,
|
||||
ctypes.byref(startupinfo),
|
||||
ctypes.byref(process_info))
|
||||
return process_info
|
||||
|
||||
|
||||
def make_inheritable(token):
|
||||
return win32api.DuplicateHandle(win32api.GetCurrentProcess(),
|
||||
token,
|
||||
win32api.GetCurrentProcess(),
|
||||
0,
|
||||
1,
|
||||
win32con.DUPLICATE_SAME_ACCESS)
|
||||
|
||||
|
||||
def runas_system(cmd, username, password):
|
||||
# This only works as system, when salt is running as a service for example
|
||||
|
||||
# Check for a domain
|
||||
def split_username(username):
|
||||
# TODO: Is there a windows api for this?
|
||||
domain = '.'
|
||||
if '@' in username:
|
||||
username, domain = username.split('@')
|
||||
if '\\' in username:
|
||||
domain, username = username.split('\\')
|
||||
return username, domain
|
||||
|
||||
# Load User and Get Token
|
||||
token = win32security.LogonUser(username,
|
||||
domain,
|
||||
password,
|
||||
win32con.LOGON32_LOGON_INTERACTIVE,
|
||||
win32con.LOGON32_PROVIDER_DEFAULT)
|
||||
|
||||
# Load the User Profile
|
||||
handle_reg = win32profile.LoadUserProfile(token, {'UserName': username})
|
||||
def runas(cmdLine, username, password=None, cwd=None):
|
||||
'''
|
||||
Run a command as another user. It the proccess is running as an admin or
|
||||
system account this method does not require a password. Other non
|
||||
priviledged accounts need to provide a password for the user to runas.
|
||||
Commands are run in with the highest level priviledges possible for the
|
||||
account provided.
|
||||
'''
|
||||
|
||||
# Elevate the token from the current process
|
||||
access = (
|
||||
win32security.TOKEN_QUERY |
|
||||
win32security.TOKEN_ADJUST_PRIVILEGES
|
||||
)
|
||||
th = win32security.OpenProcessToken(win32api.GetCurrentProcess(), access)
|
||||
salt.platform.win.elevate_token(th)
|
||||
|
||||
# Try to impersonate the SYSTEM user. This process needs to be runnung as a
|
||||
# user who as been granted the SeImpersonatePrivilege, Administrator
|
||||
# accounts have this permission by default.
|
||||
try:
|
||||
# Get Unrestricted Token (UAC) if this is an Admin Account
|
||||
elevated_token = win32security.GetTokenInformation(
|
||||
token, win32security.TokenLinkedToken)
|
||||
impersonation_token = salt.platform.win.impersonate_sid(
|
||||
salt.platform.win.SYSTEM_SID,
|
||||
session_id=0,
|
||||
privs=['SeTcbPrivilege'],
|
||||
)
|
||||
except WindowsError: # pylint: disable=undefined-variable
|
||||
log.debug("Unable to impersonate SYSTEM user")
|
||||
impersonation_token = None
|
||||
|
||||
# Get list of privileges this token contains
|
||||
privileges = win32security.GetTokenInformation(
|
||||
elevated_token, win32security.TokenPrivileges)
|
||||
# Impersonation of the SYSTEM user failed. Fallback to an un-priviledged
|
||||
# runas.
|
||||
if not impersonation_token:
|
||||
log.debug("No impersonation token, using unprivileged runas")
|
||||
return runas_unpriv(cmdLine, username, password, cwd)
|
||||
|
||||
# Create a set of all privileges to be enabled
|
||||
enable_privs = set()
|
||||
for luid, flags in privileges:
|
||||
enable_privs.add((luid, win32con.SE_PRIVILEGE_ENABLED))
|
||||
username, domain = split_username(username)
|
||||
# Validate the domain and sid exist for the username
|
||||
sid, domain, sidType = win32security.LookupAccountName(domain, username)
|
||||
if domain == 'NT AUTHORITY':
|
||||
# Logon as a system level account, SYSTEM, LOCAL SERVICE, or NETWORK
|
||||
# SERVICE.
|
||||
logonType = win32con.LOGON32_LOGON_SERVICE
|
||||
user_token = win32security.LogonUser(
|
||||
username,
|
||||
domain,
|
||||
'',
|
||||
win32con.LOGON32_LOGON_SERVICE,
|
||||
win32con.LOGON32_PROVIDER_DEFAULT,
|
||||
)
|
||||
elif password:
|
||||
# Login with a password.
|
||||
user_token = win32security.LogonUser(
|
||||
username,
|
||||
domain,
|
||||
password,
|
||||
win32con.LOGON32_LOGON_INTERACTIVE,
|
||||
win32con.LOGON32_PROVIDER_DEFAULT,
|
||||
)
|
||||
else:
|
||||
# Login without a password. This always returns an elevated token.
|
||||
user_token = salt.platform.win.logon_msv1_s4u(username).Token
|
||||
|
||||
# Enable the privileges
|
||||
win32security.AdjustTokenPrivileges(elevated_token, 0, enable_privs)
|
||||
# Get a linked user token to elevate if needed
|
||||
elevation_type = win32security.GetTokenInformation(
|
||||
user_token, win32security.TokenElevationType
|
||||
)
|
||||
if elevation_type > 1:
|
||||
user_token = win32security.GetTokenInformation(
|
||||
user_token,
|
||||
win32security.TokenLinkedToken
|
||||
)
|
||||
|
||||
except win32security.error as exc:
|
||||
# User doesn't have admin, use existing token
|
||||
if exc[0] == winerror.ERROR_NO_SUCH_LOGON_SESSION \
|
||||
or exc[0] == winerror.ERROR_PRIVILEGE_NOT_HELD:
|
||||
elevated_token = token
|
||||
else:
|
||||
raise
|
||||
# Elevate the user token
|
||||
salt.platform.win.elevate_token(user_token)
|
||||
|
||||
# Get Security Attributes
|
||||
# Make sure the user's profile is loaded.
|
||||
handle_reg = win32profile.LoadUserProfile(user_token, {'UserName': username})
|
||||
|
||||
# Make sure the user's token has access to a windows station and desktop
|
||||
salt.platform.win.grant_winsta_and_desktop(user_token)
|
||||
|
||||
# Create pipes for standard in, out and error streams
|
||||
security_attributes = win32security.SECURITY_ATTRIBUTES()
|
||||
security_attributes.bInheritHandle = 1
|
||||
|
||||
# Create a pipe to set as stdout in the child. The write handle needs to be
|
||||
# inheritable.
|
||||
stdin_read, stdin_write = win32pipe.CreatePipe(security_attributes, 0)
|
||||
stdin_read = make_inheritable(stdin_read)
|
||||
stdin_read = salt.platform.win.make_inheritable(stdin_read)
|
||||
|
||||
stdout_read, stdout_write = win32pipe.CreatePipe(security_attributes, 0)
|
||||
stdout_write = make_inheritable(stdout_write)
|
||||
stdout_write = salt.platform.win.make_inheritable(stdout_write)
|
||||
|
||||
stderr_read, stderr_write = win32pipe.CreatePipe(security_attributes, 0)
|
||||
stderr_write = make_inheritable(stderr_write)
|
||||
stderr_write = salt.platform.win.make_inheritable(stderr_write)
|
||||
|
||||
# Get startup info structure
|
||||
startup_info = win32process.STARTUPINFO()
|
||||
startup_info.dwFlags = win32con.STARTF_USESTDHANDLES
|
||||
startup_info.hStdInput = stdin_read
|
||||
startup_info.hStdOutput = stdout_write
|
||||
startup_info.hStdError = stderr_write
|
||||
# Run the process without showing a window.
|
||||
creationflags = (
|
||||
win32process.CREATE_NO_WINDOW |
|
||||
win32process.CREATE_NEW_CONSOLE |
|
||||
win32process.CREATE_SUSPENDED
|
||||
)
|
||||
|
||||
# Get User Environment
|
||||
user_environment = win32profile.CreateEnvironmentBlock(token, False)
|
||||
startup_info = salt.platform.win.STARTUPINFO(
|
||||
dwFlags=win32con.STARTF_USESTDHANDLES,
|
||||
hStdInput=stdin_read.handle,
|
||||
hStdOutput=stdout_write.handle,
|
||||
hStdError=stderr_write.handle,
|
||||
)
|
||||
|
||||
# Build command
|
||||
cmd = 'cmd /c {0}'.format(cmd)
|
||||
# Create the environment for the user
|
||||
env = win32profile.CreateEnvironmentBlock(user_token, False)
|
||||
|
||||
# Run command and return process info structure
|
||||
procArgs = (None,
|
||||
cmd,
|
||||
security_attributes,
|
||||
security_attributes,
|
||||
1,
|
||||
0,
|
||||
user_environment,
|
||||
None,
|
||||
startup_info)
|
||||
# Start the process in a suspended state.
|
||||
process_info = salt.platform.win.CreateProcessWithTokenW(
|
||||
int(user_token),
|
||||
logonflags=1,
|
||||
applicationname=None,
|
||||
commandline=cmdLine,
|
||||
currentdirectory=cwd,
|
||||
creationflags=creationflags,
|
||||
startupinfo=startup_info,
|
||||
environment=env,
|
||||
)
|
||||
|
||||
hProcess, hThread, PId, TId = \
|
||||
win32process.CreateProcessAsUser(elevated_token, *procArgs)
|
||||
hProcess = process_info.hProcess
|
||||
hThread = process_info.hThread
|
||||
dwProcessId = process_info.dwProcessId
|
||||
dwThreadId = process_info.dwThreadId
|
||||
|
||||
if stdin_read is not None:
|
||||
stdin_read.Close()
|
||||
if stdout_write is not None:
|
||||
stdout_write.Close()
|
||||
if stderr_write is not None:
|
||||
stderr_write.Close()
|
||||
hThread.Close()
|
||||
salt.platform.win.kernel32.CloseHandle(stdin_write.handle)
|
||||
salt.platform.win.kernel32.CloseHandle(stdout_write.handle)
|
||||
salt.platform.win.kernel32.CloseHandle(stderr_write.handle)
|
||||
|
||||
# Initialize ret and set first element
|
||||
ret = {'pid': PId}
|
||||
ret = {'pid': dwProcessId}
|
||||
# Resume the process
|
||||
psutil.Process(dwProcessId).resume()
|
||||
|
||||
# Get Standard Out
|
||||
fd_out = msvcrt.open_osfhandle(stdout_read, os.O_RDONLY | os.O_TEXT)
|
||||
with os.fdopen(fd_out, 'r') as f_out:
|
||||
ret['stdout'] = f_out.read()
|
||||
|
||||
# Get Standard Error
|
||||
fd_err = msvcrt.open_osfhandle(stderr_read, os.O_RDONLY | os.O_TEXT)
|
||||
with os.fdopen(fd_err, 'r') as f_err:
|
||||
ret['stderr'] = f_err.read()
|
||||
|
||||
# Get Return Code
|
||||
# Wait for the process to exit and get it's return code.
|
||||
if win32event.WaitForSingleObject(hProcess, win32event.INFINITE) == win32con.WAIT_OBJECT_0:
|
||||
exitcode = win32process.GetExitCodeProcess(hProcess)
|
||||
ret['retcode'] = exitcode
|
||||
|
||||
# Close handle to process
|
||||
win32api.CloseHandle(hProcess)
|
||||
# Read standard out
|
||||
fd_out = msvcrt.open_osfhandle(stdout_read.handle, os.O_RDONLY | os.O_TEXT)
|
||||
with os.fdopen(fd_out, 'r') as f_out:
|
||||
stdout = f_out.read()
|
||||
ret['stdout'] = stdout
|
||||
|
||||
# Unload the User Profile
|
||||
win32profile.UnloadUserProfile(token, handle_reg)
|
||||
# Read standard error
|
||||
fd_err = msvcrt.open_osfhandle(stderr_read.handle, os.O_RDONLY | os.O_TEXT)
|
||||
with os.fdopen(fd_err, 'r') as f_err:
|
||||
stderr = f_err.read()
|
||||
ret['stderr'] = stderr
|
||||
|
||||
win32profile.UnloadUserProfile(user_token, handle_reg)
|
||||
|
||||
salt.platform.win.kernel32.CloseHandle(hProcess)
|
||||
win32api.CloseHandle(user_token)
|
||||
if impersonation_token:
|
||||
win32security.RevertToSelf()
|
||||
win32api.CloseHandle(impersonation_token)
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def runas(cmd, username, password, cwd=None):
|
||||
# This only works when not running under the system account
|
||||
# Debug mode for example
|
||||
if salt.utils.win_functions.get_current_user() == 'SYSTEM':
|
||||
return runas_system(cmd, username, password)
|
||||
def runas_unpriv(cmd, username, password, cwd=None):
|
||||
'''
|
||||
Runas that works for non-priviledged users
|
||||
'''
|
||||
|
||||
# Create a pipe to set as stdout in the child. The write handle needs to be
|
||||
# inheritable.
|
||||
c2pread, c2pwrite = CreatePipe(inherit_read=False, inherit_write=True)
|
||||
errread, errwrite = CreatePipe(inherit_read=False, inherit_write=True)
|
||||
c2pread, c2pwrite = salt.platform.win.CreatePipe(
|
||||
inherit_read=False, inherit_write=True,
|
||||
)
|
||||
errread, errwrite = salt.platform.win.CreatePipe(
|
||||
inherit_read=False, inherit_write=True,
|
||||
)
|
||||
|
||||
# Create inheritable copy of the stdin
|
||||
stdin = kernel32.GetStdHandle(STD_INPUT_HANDLE)
|
||||
dupin = DuplicateHandle(srchandle=stdin, inherit=True)
|
||||
stdin = salt.platform.win.kernel32.GetStdHandle(
|
||||
salt.platform.win.STD_INPUT_HANDLE,
|
||||
)
|
||||
dupin = salt.platform.win.DuplicateHandle(srchandle=stdin, inherit=True)
|
||||
|
||||
# Get startup info structure
|
||||
startup_info = STARTUPINFO(dwFlags=win32con.STARTF_USESTDHANDLES,
|
||||
hStdInput=dupin,
|
||||
hStdOutput=c2pwrite,
|
||||
hStdError=errwrite)
|
||||
startup_info = salt.platform.win.STARTUPINFO(
|
||||
dwFlags=win32con.STARTF_USESTDHANDLES,
|
||||
hStdInput=dupin,
|
||||
hStdOutput=c2pwrite,
|
||||
hStdError=errwrite,
|
||||
)
|
||||
|
||||
# Build command
|
||||
cmd = 'cmd /c {0}'.format(cmd)
|
||||
|
||||
# Check for a domain
|
||||
domain = None
|
||||
if '@' in username:
|
||||
username, domain = username.split('@')
|
||||
if '\\' in username:
|
||||
domain, username = username.split('\\')
|
||||
username, domain = split_username(username)
|
||||
|
||||
# Run command and return process info structure
|
||||
process_info = CreateProcessWithLogonW(username=username,
|
||||
domain=domain,
|
||||
password=password,
|
||||
logonflags=LOGON_WITH_PROFILE,
|
||||
commandline=cmd,
|
||||
startupinfo=startup_info,
|
||||
currentdirectory=cwd)
|
||||
process_info = salt.platform.win.CreateProcessWithLogonW(
|
||||
username=username,
|
||||
domain=domain,
|
||||
password=password,
|
||||
logonflags=salt.platform.win.LOGON_WITH_PROFILE,
|
||||
commandline=cmd,
|
||||
startupinfo=startup_info,
|
||||
currentdirectory=cwd)
|
||||
|
||||
kernel32.CloseHandle(dupin)
|
||||
kernel32.CloseHandle(c2pwrite)
|
||||
kernel32.CloseHandle(errwrite)
|
||||
kernel32.CloseHandle(process_info.hThread)
|
||||
salt.platform.win.kernel32.CloseHandle(dupin)
|
||||
salt.platform.win.kernel32.CloseHandle(c2pwrite)
|
||||
salt.platform.win.kernel32.CloseHandle(errwrite)
|
||||
salt.platform.win.kernel32.CloseHandle(process_info.hThread)
|
||||
|
||||
# Initialize ret and set first element
|
||||
ret = {'pid': process_info.dwProcessId}
|
||||
@ -452,14 +279,14 @@ def runas(cmd, username, password, cwd=None):
|
||||
ret['stderr'] = f_err.read()
|
||||
|
||||
# Get Return Code
|
||||
if kernel32.WaitForSingleObject(process_info.hProcess, INFINITE) == \
|
||||
if salt.platform.win.kernel32.WaitForSingleObject(process_info.hProcess, win32event.INFINITE) == \
|
||||
win32con.WAIT_OBJECT_0:
|
||||
exitcode = wintypes.DWORD()
|
||||
kernel32.GetExitCodeProcess(process_info.hProcess,
|
||||
exitcode = salt.platform.win.wintypes.DWORD()
|
||||
salt.platform.win.kernel32.GetExitCodeProcess(process_info.hProcess,
|
||||
ctypes.byref(exitcode))
|
||||
ret['retcode'] = exitcode.value
|
||||
|
||||
# Close handle to process
|
||||
kernel32.CloseHandle(process_info.hProcess)
|
||||
salt.platform.win.kernel32.CloseHandle(process_info.hProcess)
|
||||
|
||||
return ret
|
||||
|
618
tests/integration/utils/test_win_runas.py
Normal file
618
tests/integration/utils/test_win_runas.py
Normal file
@ -0,0 +1,618 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
import textwrap
|
||||
import subprocess
|
||||
import socket
|
||||
import inspect
|
||||
import io
|
||||
|
||||
# Service manager imports
|
||||
import sys
|
||||
import os
|
||||
import logging
|
||||
import time
|
||||
import threading
|
||||
import win32service
|
||||
import win32serviceutil
|
||||
import win32event
|
||||
import servicemanager
|
||||
import traceback
|
||||
import time
|
||||
|
||||
import yaml
|
||||
from tests.support.case import ModuleCase
|
||||
from tests.support.paths import CODE_DIR
|
||||
|
||||
from tests.support.helpers import (
|
||||
with_system_user,
|
||||
)
|
||||
import salt.utils.win_runas
|
||||
import salt.ext.six
|
||||
|
||||
try:
|
||||
import win32api
|
||||
CODE_DIR = win32api.GetLongPathName(CODE_DIR)
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
PASSWORD = 'P@ssW0rd'
|
||||
NOPRIV_STDERR = 'ERROR: Logged-on user does not have administrative privilege.\n'
|
||||
PRIV_STDOUT = (
|
||||
'\nINFO: The system global flag \'maintain objects list\' needs\n '
|
||||
'to be enabled to see local opened files.\n See Openfiles '
|
||||
'/? for more information.\n\n\nFiles opened remotely via local share '
|
||||
'points:\n---------------------------------------------\n\n'
|
||||
'INFO: No shared open files found.\n'
|
||||
)
|
||||
RUNAS_PATH = os.path.abspath(os.path.join(CODE_DIR, 'runas.py'))
|
||||
RUNAS_OUT = os.path.abspath(os.path.join(CODE_DIR, 'runas.out'))
|
||||
|
||||
|
||||
def default_target(service, *args, **kwargs):
|
||||
while service.active:
|
||||
time.sleep(service.timeout)
|
||||
|
||||
|
||||
class _ServiceManager(win32serviceutil.ServiceFramework):
|
||||
'''
|
||||
A windows service manager
|
||||
'''
|
||||
|
||||
_svc_name_ = "Service Manager"
|
||||
_svc_display_name_ = "Service Manager"
|
||||
_svc_description_ = "A Service Manager"
|
||||
run_in_foreground = False
|
||||
target = default_target
|
||||
|
||||
def __init__(self, args, target=None, timeout=60, active=True):
|
||||
win32serviceutil.ServiceFramework.__init__(self, args)
|
||||
self.hWaitStop = win32event.CreateEvent(None, 0, 0, None)
|
||||
self.timeout = timeout
|
||||
self.active = active
|
||||
if target is not None:
|
||||
self.target = target
|
||||
|
||||
@classmethod
|
||||
def log_error(cls, msg):
|
||||
if cls.run_in_foreground:
|
||||
logger.error(msg)
|
||||
servicemanager.LogErrorMsg(msg)
|
||||
|
||||
@classmethod
|
||||
def log_info(cls, msg):
|
||||
if cls.run_in_foreground:
|
||||
logger.info(msg)
|
||||
servicemanager.LogInfoMsg(msg)
|
||||
|
||||
@classmethod
|
||||
def log_exception(cls, msg):
|
||||
if cls.run_in_foreground:
|
||||
logger.exception(msg)
|
||||
exc_info = sys.exc_info()
|
||||
tb = traceback.format_tb(exc_info[2])
|
||||
servicemanager.LogErrorMsg("{} {} {}".format(msg, exc_info[1], tb))
|
||||
|
||||
@property
|
||||
def timeout_ms(self):
|
||||
return self.timeout * 1000
|
||||
|
||||
def SvcStop(self):
|
||||
"""
|
||||
Stop the service by; terminating any subprocess call, notify
|
||||
windows internals of the stop event, set the instance's active
|
||||
attribute to 'False' so the run loops stop.
|
||||
"""
|
||||
self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
|
||||
win32event.SetEvent(self.hWaitStop)
|
||||
self.active = False
|
||||
|
||||
def SvcDoRun(self):
|
||||
"""
|
||||
Run the monitor in a separete thread so the main thread is
|
||||
free to react to events sent to the windows service.
|
||||
"""
|
||||
servicemanager.LogMsg(
|
||||
servicemanager.EVENTLOG_INFORMATION_TYPE,
|
||||
servicemanager.PYS_SERVICE_STARTED,
|
||||
(self._svc_name_, ''),
|
||||
)
|
||||
self.log_info("Starting Service {}".format(self._svc_name_))
|
||||
monitor_thread = threading.Thread(target=self.target_thread)
|
||||
monitor_thread.start()
|
||||
while self.active:
|
||||
rc = win32event.WaitForSingleObject(self.hWaitStop, self.timeout_ms)
|
||||
if rc == win32event.WAIT_OBJECT_0:
|
||||
# Stop signal encountered
|
||||
self.log_info("Stopping Service")
|
||||
break
|
||||
if not monitor_thread.is_alive():
|
||||
self.log_info("Update Thread Died, Stopping Service")
|
||||
break
|
||||
|
||||
def target_thread(self, *args, **kwargs):
|
||||
"""
|
||||
Target Thread, handles any exception in the target method and
|
||||
logs them.
|
||||
"""
|
||||
self.log_info("Monitor")
|
||||
try:
|
||||
self.target(self, *args, **kwargs)
|
||||
except Exception as exc:
|
||||
# TODO: Add traceback info to windows event log objects
|
||||
self.log_exception("Exception In Target")
|
||||
|
||||
@classmethod
|
||||
def install(cls, username=None, password=None, start_type=None):
|
||||
if hasattr(cls, '_svc_reg_class_'):
|
||||
svc_class = cls._svc_reg_class_
|
||||
else:
|
||||
svc_class = win32serviceutil.GetServiceClassString(cls)
|
||||
win32serviceutil.InstallService(
|
||||
svc_class,
|
||||
cls._svc_name_,
|
||||
cls._svc_display_name_,
|
||||
description=cls._svc_description_,
|
||||
userName=username,
|
||||
password=password,
|
||||
startType=start_type,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def remove(cls):
|
||||
win32serviceutil.RemoveService(
|
||||
cls._svc_name_
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def start(cls):
|
||||
win32serviceutil.StartService(
|
||||
cls._svc_name_
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def restart(cls):
|
||||
win32serviceutil.RestartService(
|
||||
cls._svc_name_
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def stop(cls):
|
||||
win32serviceutil.StopService(
|
||||
cls._svc_name_
|
||||
)
|
||||
|
||||
|
||||
def service_class_factory(cls_name, name, target=default_target, display_name='', description='', run_in_foreground=False):
|
||||
frm = inspect.stack()[1]
|
||||
mod = inspect.getmodule(frm[0])
|
||||
if salt.ext.six.PY2:
|
||||
cls_name = cls_name.encode()
|
||||
return type(
|
||||
cls_name,
|
||||
(_ServiceManager, object),
|
||||
{
|
||||
'__module__': mod.__name__,
|
||||
'_svc_name_': name,
|
||||
'_svc_display_name_': display_name or name,
|
||||
'_svc_description_': description,
|
||||
'run_in_foreground': run_in_foreground,
|
||||
'target': target,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
test_service = service_class_factory('test_service', 'test service')
|
||||
|
||||
|
||||
SERVICE_SOURCE = '''
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
import logging
|
||||
logger = logging.getLogger()
|
||||
logging.basicConfig(level=logging.DEBUG, format="%(message)s")
|
||||
|
||||
from tests.integration.utils.test_win_runas import service_class_factory
|
||||
import salt.utils.win_runas
|
||||
import sys
|
||||
import yaml
|
||||
|
||||
OUTPUT = {}
|
||||
USERNAME = '{}'
|
||||
PASSWORD = '{}'
|
||||
|
||||
|
||||
def target(service, *args, **kwargs):
|
||||
service.log_info("target start")
|
||||
if PASSWORD:
|
||||
ret = salt.utils.win_runas.runas(
|
||||
'cmd.exe /C OPENFILES',
|
||||
username=USERNAME,
|
||||
password=PASSWORD,
|
||||
)
|
||||
else:
|
||||
ret = salt.utils.win_runas.runas(
|
||||
'cmd.exe /C OPENFILES',
|
||||
username=USERNAME,
|
||||
)
|
||||
|
||||
service.log_info("win_runas returned %s" % ret)
|
||||
with open(OUTPUT, 'w') as fp:
|
||||
yaml.dump(ret, fp)
|
||||
service.log_info("target stop")
|
||||
|
||||
|
||||
# This class will get imported and run as the service
|
||||
test_service = service_class_factory('test_service', 'test service', target=target)
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
test_service.stop()
|
||||
except Exception as exc:
|
||||
logger.debug("stop service failed, this is ok.")
|
||||
try:
|
||||
test_service.remove()
|
||||
except Exception as exc:
|
||||
logger.debug("remove service failed, this os ok.")
|
||||
test_service.install()
|
||||
sys.exit(0)
|
||||
'''
|
||||
|
||||
|
||||
def wait_for_service(name, timeout=200):
|
||||
start = time.time()
|
||||
while True:
|
||||
status = win32serviceutil.QueryServiceStatus(name)
|
||||
if status[1] == win32service.SERVICE_STOPPED:
|
||||
break
|
||||
if time.time() - start > timeout:
|
||||
raise TimeoutError("Timeout waiting for service") # pylint: disable=undefined-variable
|
||||
|
||||
time.sleep(.3)
|
||||
|
||||
|
||||
class RunAsTest(ModuleCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(RunAsTest, cls).setUpClass()
|
||||
cls.hostname = socket.gethostname()
|
||||
|
||||
@with_system_user('test-runas', on_existing='delete', delete=True,
|
||||
password=PASSWORD)
|
||||
def test_runas(self, username):
|
||||
ret = salt.utils.win_runas.runas('cmd.exe /C OPENFILES', username, PASSWORD)
|
||||
self.assertEqual(ret['stdout'], '')
|
||||
self.assertEqual(ret['stderr'], NOPRIV_STDERR)
|
||||
self.assertEqual(ret['retcode'], 1)
|
||||
|
||||
@with_system_user('test-runas', on_existing='delete', delete=True,
|
||||
password=PASSWORD)
|
||||
def test_runas_no_pass(self, username):
|
||||
ret = salt.utils.win_runas.runas('cmd.exe /C OPENFILES', username)
|
||||
self.assertEqual(ret['stdout'], '')
|
||||
self.assertEqual(ret['stderr'], NOPRIV_STDERR)
|
||||
self.assertEqual(ret['retcode'], 1)
|
||||
|
||||
@with_system_user('test-runas-admin', on_existing='delete', delete=True,
|
||||
password=PASSWORD, groups=['Administrators'])
|
||||
def test_runas_admin(self, username):
|
||||
ret = salt.utils.win_runas.runas('cmd.exe /C OPENFILES', username, PASSWORD)
|
||||
self.assertEqual(ret['stdout'], PRIV_STDOUT)
|
||||
self.assertEqual(ret['stderr'], '')
|
||||
self.assertEqual(ret['retcode'], 0)
|
||||
|
||||
@with_system_user('test-runas-admin', on_existing='delete', delete=True,
|
||||
password=PASSWORD, groups=['Administrators'])
|
||||
def test_runas_admin_no_pass(self, username):
|
||||
ret = salt.utils.win_runas.runas('cmd.exe /C OPENFILES', username)
|
||||
self.assertEqual(ret['stdout'], PRIV_STDOUT)
|
||||
self.assertEqual(ret['stderr'], '')
|
||||
self.assertEqual(ret['retcode'], 0)
|
||||
|
||||
def test_runas_system_user(self):
|
||||
ret = salt.utils.win_runas.runas('cmd.exe /C OPENFILES', 'SYSTEM')
|
||||
self.assertEqual(ret['stdout'], PRIV_STDOUT)
|
||||
self.assertEqual(ret['stderr'], '')
|
||||
self.assertEqual(ret['retcode'], 0)
|
||||
|
||||
def test_runas_network_service(self):
|
||||
ret = salt.utils.win_runas.runas('cmd.exe /C OPENFILES', 'NETWORK SERVICE')
|
||||
self.assertEqual(ret['stdout'], '')
|
||||
self.assertEqual(ret['stderr'], NOPRIV_STDERR)
|
||||
self.assertEqual(ret['retcode'], 1)
|
||||
|
||||
def test_runas_local_service(self):
|
||||
ret = salt.utils.win_runas.runas('cmd.exe /C OPENFILES', 'LOCAL SERVICE')
|
||||
self.assertEqual(ret['stdout'], '')
|
||||
self.assertEqual(ret['stderr'], NOPRIV_STDERR)
|
||||
self.assertEqual(ret['retcode'], 1)
|
||||
|
||||
@with_system_user('test-runas', on_existing='delete', delete=True,
|
||||
password=PASSWORD)
|
||||
def test_runas_winrs(self, username):
|
||||
runaspy = textwrap.dedent('''
|
||||
import sys
|
||||
import salt.utils.win_runas
|
||||
username = '{}'
|
||||
password = '{}'
|
||||
sys.exit(salt.utils.win_runas.runas('cmd.exe /C OPENFILES', username, password)['retcode'])
|
||||
'''.format(username, PASSWORD))
|
||||
with salt.utils.fopen(RUNAS_PATH, 'w') as fp:
|
||||
fp.write(runaspy)
|
||||
ret = subprocess.call("cmd.exe /C winrs /r:{} python {}".format(
|
||||
self.hostname, RUNAS_PATH), shell=True)
|
||||
self.assertEqual(ret, 1)
|
||||
|
||||
@with_system_user('test-runas', on_existing='delete', delete=True,
|
||||
password=PASSWORD)
|
||||
def test_runas_winrs_no_pass(self, username):
|
||||
runaspy = textwrap.dedent('''
|
||||
import sys
|
||||
import salt.utils.win_runas
|
||||
username = '{}'
|
||||
sys.exit(salt.utils.win_runas.runas('cmd.exe /C OPENFILES', username)['retcode'])
|
||||
'''.format(username))
|
||||
with salt.utils.fopen(RUNAS_PATH, 'w') as fp:
|
||||
fp.write(runaspy)
|
||||
ret = subprocess.call("cmd.exe /C winrs /r:{} python {}".format(
|
||||
self.hostname, RUNAS_PATH), shell=True)
|
||||
self.assertEqual(ret, 1)
|
||||
|
||||
@with_system_user('test-runas-admin', on_existing='delete', delete=True,
|
||||
password=PASSWORD, groups=['Administrators'])
|
||||
def test_runas_winrs_admin(self, username):
|
||||
runaspy = textwrap.dedent('''
|
||||
import sys
|
||||
import salt.utils.win_runas
|
||||
username = '{}'
|
||||
password = '{}'
|
||||
sys.exit(salt.utils.win_runas.runas('cmd.exe /C OPENFILES', username, password)['retcode'])
|
||||
'''.format(username, PASSWORD))
|
||||
with salt.utils.fopen(RUNAS_PATH, 'w') as fp:
|
||||
fp.write(runaspy)
|
||||
ret = subprocess.call("cmd.exe /C winrs /r:{} python {}".format(
|
||||
self.hostname, RUNAS_PATH), shell=True)
|
||||
self.assertEqual(ret, 0)
|
||||
|
||||
@with_system_user('test-runas-admin', on_existing='delete', delete=True,
|
||||
password=PASSWORD, groups=['Administrators'])
|
||||
def test_runas_winrs_admin_no_pass(self, username):
|
||||
runaspy = textwrap.dedent('''
|
||||
import sys
|
||||
import salt.utils.win_runas
|
||||
username = '{}'
|
||||
sys.exit(salt.utils.win_runas.runas('cmd.exe /C OPENFILES', username)['retcode'])
|
||||
'''.format(username))
|
||||
with salt.utils.fopen(RUNAS_PATH, 'w') as fp:
|
||||
fp.write(runaspy)
|
||||
ret = subprocess.call("cmd.exe /C winrs /r:{} python {}".format(
|
||||
self.hostname, RUNAS_PATH), shell=True)
|
||||
self.assertEqual(ret, 0)
|
||||
|
||||
def test_runas_winrs_system_user(self):
|
||||
runaspy = textwrap.dedent('''
|
||||
import sys
|
||||
import salt.utils.win_runas
|
||||
sys.exit(salt.utils.win_runas.runas('cmd.exe /C OPENFILES', 'SYSTEM')['retcode'])
|
||||
''')
|
||||
with salt.utils.fopen(RUNAS_PATH, 'w') as fp:
|
||||
fp.write(runaspy)
|
||||
ret = subprocess.call("cmd.exe /C winrs /r:{} python {}".format(
|
||||
self.hostname, RUNAS_PATH), shell=True)
|
||||
self.assertEqual(ret, 0)
|
||||
|
||||
def test_runas_winrs_network_service_user(self):
|
||||
runaspy = textwrap.dedent('''
|
||||
import sys
|
||||
import salt.utils.win_runas
|
||||
sys.exit(salt.utils.win_runas.runas('cmd.exe /C OPENFILES', 'NETWORK SERVICE')['retcode'])
|
||||
''')
|
||||
with salt.utils.fopen(RUNAS_PATH, 'w') as fp:
|
||||
fp.write(runaspy)
|
||||
ret = subprocess.call("cmd.exe /C winrs /r:{} python {}".format(
|
||||
self.hostname, RUNAS_PATH), shell=True)
|
||||
self.assertEqual(ret, 1)
|
||||
|
||||
def test_runas_winrs_local_service_user(self):
|
||||
runaspy = textwrap.dedent('''
|
||||
import sys
|
||||
import salt.utils.win_runas
|
||||
sys.exit(salt.utils.win_runas.runas('cmd.exe /C OPENFILES', 'LOCAL SERVICE')['retcode'])
|
||||
''')
|
||||
with salt.utils.fopen(RUNAS_PATH, 'w') as fp:
|
||||
fp.write(runaspy)
|
||||
ret = subprocess.call("cmd.exe /C winrs /r:{} python {}".format(
|
||||
self.hostname, RUNAS_PATH), shell=True)
|
||||
self.assertEqual(ret, 1)
|
||||
|
||||
@with_system_user('test-runas', on_existing='delete', delete=True,
|
||||
password=PASSWORD)
|
||||
def test_runas_powershell_remoting(self, username):
|
||||
psrp_wrap = (
|
||||
'powershell Invoke-Command -ComputerName {} -ScriptBlock {{ {} }}'
|
||||
)
|
||||
runaspy = textwrap.dedent('''
|
||||
import sys
|
||||
import salt.utils.win_runas
|
||||
username = '{}'
|
||||
password = '{}'
|
||||
sys.exit(salt.utils.win_runas.runas('cmd.exe /C OPENFILES', username, password)['retcode'])
|
||||
'''.format(username, PASSWORD))
|
||||
with salt.utils.fopen(RUNAS_PATH, 'w') as fp:
|
||||
fp.write(runaspy)
|
||||
cmd = 'python.exe {}'.format(RUNAS_PATH)
|
||||
ret = subprocess.call(
|
||||
psrp_wrap.format(self.hostname, cmd),
|
||||
shell=True
|
||||
)
|
||||
self.assertEqual(ret, 1)
|
||||
|
||||
@with_system_user('test-runas', on_existing='delete', delete=True,
|
||||
password=PASSWORD)
|
||||
def test_runas_powershell_remoting_no_pass(self, username):
|
||||
psrp_wrap = (
|
||||
'powershell Invoke-Command -ComputerName {} -ScriptBlock {{ {} }}'
|
||||
)
|
||||
runaspy = textwrap.dedent('''
|
||||
import sys
|
||||
import salt.utils.win_runas
|
||||
username = '{}'
|
||||
sys.exit(salt.utils.win_runas.runas('cmd.exe /C OPENFILES', username)['retcode'])
|
||||
'''.format(username))
|
||||
with salt.utils.fopen(RUNAS_PATH, 'w') as fp:
|
||||
fp.write(runaspy)
|
||||
cmd = 'python.exe {}'.format(RUNAS_PATH)
|
||||
ret = subprocess.call(
|
||||
psrp_wrap.format(self.hostname, cmd),
|
||||
shell=True
|
||||
)
|
||||
self.assertEqual(ret, 1)
|
||||
|
||||
@with_system_user('test-runas-admin', on_existing='delete', delete=True,
|
||||
password=PASSWORD, groups=['Administrators'])
|
||||
def test_runas_powershell_remoting_admin(self, username):
|
||||
psrp_wrap = (
|
||||
'powershell Invoke-Command -ComputerName {} -ScriptBlock {{ {} }}; exit $LASTEXITCODE'
|
||||
)
|
||||
runaspy = textwrap.dedent('''
|
||||
import sys
|
||||
import salt.utils.win_runas
|
||||
username = '{}'
|
||||
password = '{}'
|
||||
ret = salt.utils.win_runas.runas('cmd.exe /C OPENFILES', username, password)
|
||||
sys.exit(ret['retcode'])
|
||||
'''.format(username, PASSWORD))
|
||||
with salt.utils.fopen(RUNAS_PATH, 'w') as fp:
|
||||
fp.write(runaspy)
|
||||
cmd = 'python.exe {}; exit $LASTEXITCODE'.format(RUNAS_PATH)
|
||||
ret = subprocess.call(
|
||||
psrp_wrap.format(self.hostname, cmd),
|
||||
shell=True
|
||||
)
|
||||
self.assertEqual(ret, 0)
|
||||
|
||||
@with_system_user('test-runas-admin', on_existing='delete', delete=True,
|
||||
password=PASSWORD, groups=['Administrators'])
|
||||
def test_runas_powershell_remoting_admin_no_pass(self, username):
|
||||
psrp_wrap = (
|
||||
'powershell Invoke-Command -ComputerName {} -ScriptBlock {{ {} }}; exit $LASTEXITCODE'
|
||||
)
|
||||
runaspy = textwrap.dedent('''
|
||||
import sys
|
||||
import salt.utils.win_runas
|
||||
username = '{}'
|
||||
sys.exit(salt.utils.win_runas.runas('cmd.exe /C OPENFILES', username)['retcode'])
|
||||
'''.format(username))
|
||||
with salt.utils.fopen(RUNAS_PATH, 'w') as fp:
|
||||
fp.write(runaspy)
|
||||
cmd = 'python.exe {}; exit $LASTEXITCODE'.format(RUNAS_PATH)
|
||||
ret = subprocess.call(
|
||||
psrp_wrap.format(self.hostname, cmd),
|
||||
shell=True
|
||||
)
|
||||
self.assertEqual(ret, 0)
|
||||
|
||||
@with_system_user('test-runas', on_existing='delete', delete=True,
|
||||
password=PASSWORD)
|
||||
def test_runas_service(self, username, timeout=200):
|
||||
if os.path.exists(RUNAS_OUT):
|
||||
os.remove(RUNAS_OUT)
|
||||
assert not os.path.exists(RUNAS_OUT)
|
||||
runaspy = SERVICE_SOURCE.format(repr(RUNAS_OUT), username, PASSWORD)
|
||||
with io.open(RUNAS_PATH, 'w', encoding='utf-8') as fp:
|
||||
fp.write(runaspy)
|
||||
cmd = 'python.exe {}'.format(RUNAS_PATH)
|
||||
ret = subprocess.call(
|
||||
cmd,
|
||||
shell=True
|
||||
)
|
||||
self.assertEqual(ret, 0)
|
||||
win32serviceutil.StartService('test service')
|
||||
wait_for_service('test service')
|
||||
with salt.utils.fopen(RUNAS_OUT, 'r') as fp:
|
||||
ret = yaml.load(fp)
|
||||
assert ret['retcode'] == 1, ret
|
||||
|
||||
@with_system_user('test-runas', on_existing='delete', delete=True,
|
||||
password=PASSWORD)
|
||||
def test_runas_service_no_pass(self, username, timeout=200):
|
||||
if os.path.exists(RUNAS_OUT):
|
||||
os.remove(RUNAS_OUT)
|
||||
assert not os.path.exists(RUNAS_OUT)
|
||||
runaspy = SERVICE_SOURCE.format(repr(RUNAS_OUT), username, '')
|
||||
with io.open(RUNAS_PATH, 'w', encoding='utf-8') as fp:
|
||||
fp.write(runaspy)
|
||||
cmd = 'python.exe {}'.format(RUNAS_PATH)
|
||||
ret = subprocess.call(
|
||||
cmd,
|
||||
shell=True
|
||||
)
|
||||
self.assertEqual(ret, 0)
|
||||
win32serviceutil.StartService('test service')
|
||||
wait_for_service('test service')
|
||||
with salt.utils.fopen(RUNAS_OUT, 'r') as fp:
|
||||
ret = yaml.load(fp)
|
||||
assert ret['retcode'] == 1, ret
|
||||
|
||||
@with_system_user('test-runas-admin', on_existing='delete', delete=True,
|
||||
password=PASSWORD, groups=['Administrators'])
|
||||
def test_runas_service_admin(self, username, timeout=200):
|
||||
if os.path.exists(RUNAS_OUT):
|
||||
os.remove(RUNAS_OUT)
|
||||
assert not os.path.exists(RUNAS_OUT)
|
||||
runaspy = SERVICE_SOURCE.format(repr(RUNAS_OUT), username, PASSWORD)
|
||||
with io.open(RUNAS_PATH, 'w', encoding='utf-8') as fp:
|
||||
fp.write(runaspy)
|
||||
cmd = 'python.exe {}'.format(RUNAS_PATH)
|
||||
ret = subprocess.call(
|
||||
cmd,
|
||||
shell=True
|
||||
)
|
||||
self.assertEqual(ret, 0)
|
||||
win32serviceutil.StartService('test service')
|
||||
wait_for_service('test service')
|
||||
with salt.utils.fopen(RUNAS_OUT, 'r') as fp:
|
||||
ret = yaml.load(fp)
|
||||
assert ret['retcode'] == 0, ret
|
||||
|
||||
@with_system_user('test-runas-admin', on_existing='delete', delete=True,
|
||||
password=PASSWORD, groups=['Administrators'])
|
||||
def test_runas_service_admin_no_pass(self, username, timeout=200):
|
||||
if os.path.exists(RUNAS_OUT):
|
||||
os.remove(RUNAS_OUT)
|
||||
assert not os.path.exists(RUNAS_OUT)
|
||||
runaspy = SERVICE_SOURCE.format(repr(RUNAS_OUT), username, '')
|
||||
with io.open(RUNAS_PATH, 'w', encoding='utf-8') as fp:
|
||||
fp.write(runaspy)
|
||||
cmd = 'python.exe {}'.format(RUNAS_PATH)
|
||||
ret = subprocess.call(
|
||||
cmd,
|
||||
shell=True
|
||||
)
|
||||
self.assertEqual(ret, 0)
|
||||
win32serviceutil.StartService('test service')
|
||||
wait_for_service('test service')
|
||||
with salt.utils.fopen(RUNAS_OUT, 'r') as fp:
|
||||
ret = yaml.load(fp)
|
||||
assert ret['retcode'] == 0, ret
|
||||
|
||||
def test_runas_service_system_user(self):
|
||||
if os.path.exists(RUNAS_OUT):
|
||||
os.remove(RUNAS_OUT)
|
||||
assert not os.path.exists(RUNAS_OUT)
|
||||
runaspy = SERVICE_SOURCE.format(repr(RUNAS_OUT), 'SYSTEM', '')
|
||||
with io.open(RUNAS_PATH, 'w', encoding='utf-8') as fp:
|
||||
fp.write(runaspy)
|
||||
cmd = 'python.exe {}'.format(RUNAS_PATH)
|
||||
ret = subprocess.call(
|
||||
cmd,
|
||||
shell=True
|
||||
)
|
||||
self.assertEqual(ret, 0)
|
||||
win32serviceutil.StartService('test service')
|
||||
wait_for_service('test service')
|
||||
with salt.utils.fopen(RUNAS_OUT, 'r') as fp:
|
||||
ret = yaml.load(fp)
|
||||
assert ret['retcode'] == 0, ret
|
@ -618,7 +618,7 @@ def requires_network(only_local_network=False):
|
||||
return decorator
|
||||
|
||||
|
||||
def with_system_user(username, on_existing='delete', delete=True, password=None):
|
||||
def with_system_user(username, on_existing='delete', delete=True, password=None, groups=None):
|
||||
'''
|
||||
Create and optionally destroy a system user to be used within a test
|
||||
case. The system user is created using the ``user`` salt module.
|
||||
@ -651,7 +651,7 @@ def with_system_user(username, on_existing='delete', delete=True, password=None)
|
||||
|
||||
# Let's add the user to the system.
|
||||
log.debug('Creating system user {0!r}'.format(username))
|
||||
kwargs = {'timeout': 60}
|
||||
kwargs = {'timeout': 60, 'groups': groups}
|
||||
if salt.utils.platform.is_windows():
|
||||
kwargs.update({'password': password})
|
||||
create_user = cls.run_function('user.add', [username], **kwargs)
|
||||
@ -685,7 +685,7 @@ def with_system_user(username, on_existing='delete', delete=True, password=None)
|
||||
username
|
||||
)
|
||||
)
|
||||
create_user = cls.run_function('user.add', [username])
|
||||
create_user = cls.run_function('user.add', [username], **kwargs)
|
||||
if not create_user:
|
||||
cls.skipTest(
|
||||
'A user named {0!r} already existed, was deleted '
|
||||
|
Loading…
Reference in New Issue
Block a user