win_script: add support for become and centralise exec wrapper builder (#45582)

* win_script: add support for become and centralise exec wrapper builder

* satisfying the pep8 gods

* do not scan for module dependencies when running as a script
This commit is contained in:
Jordan Borean 2018-09-13 08:50:13 +10:00 committed by GitHub
parent 03dbb1d9c4
commit d81249994e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 101 additions and 73 deletions

View file

@ -0,0 +1,2 @@
minor_changes:
- win_script - added support for running a script with become

View file

@ -688,6 +688,69 @@ def _is_binary(b_module_data):
return bool(start.translate(None, textchars))
def _create_powershell_wrapper(b_module_data, module_args, environment,
async_timeout, become, become_method,
become_user, become_password, become_flags,
scan_dependencies=True):
# creates the manifest/wrapper used in PowerShell modules to enable things
# like become and async - this is also called in action/script.py
exec_manifest = dict(
module_entry=to_text(base64.b64encode(b_module_data)),
powershell_modules=dict(),
module_args=module_args,
actions=['exec'],
environment=environment
)
exec_manifest['exec'] = to_text(base64.b64encode(to_bytes(leaf_exec)))
if async_timeout > 0:
exec_manifest["actions"].insert(0, 'async_watchdog')
exec_manifest["async_watchdog"] = to_text(
base64.b64encode(to_bytes(async_watchdog)))
exec_manifest["actions"].insert(0, 'async_wrapper')
exec_manifest["async_wrapper"] = to_text(
base64.b64encode(to_bytes(async_wrapper)))
exec_manifest["async_jid"] = str(random.randint(0, 999999999999))
exec_manifest["async_timeout_sec"] = async_timeout
if become and become_method == 'runas':
exec_manifest["actions"].insert(0, 'become')
exec_manifest["become_user"] = become_user
exec_manifest["become_password"] = become_password
exec_manifest['become_flags'] = become_flags
exec_manifest["become"] = to_text(
base64.b64encode(to_bytes(become_wrapper)))
finder = PSModuleDepFinder()
# we don't want to scan for any module_utils or other module related flags
# if scan_dependencies=False - action/script sets to False
if scan_dependencies:
finder.scan_module(b_module_data)
for name, data in finder.modules.items():
b64_data = to_text(base64.b64encode(data))
exec_manifest['powershell_modules'][name] = b64_data
exec_manifest['min_ps_version'] = finder.ps_version
exec_manifest['min_os_version'] = finder.os_version
if finder.become and 'become' not in exec_manifest['actions']:
exec_manifest['actions'].insert(0, 'become')
exec_manifest['become_user'] = 'SYSTEM'
exec_manifest['become_password'] = None
exec_manifest['become_flags'] = None
exec_manifest['become'] = to_text(
base64.b64encode(to_bytes(become_wrapper)))
# FUTURE: smuggle this back as a dict instead of serializing here;
# the connection plugin may need to modify it
b_json = to_bytes(json.dumps(exec_manifest))
b_data = exec_wrapper.replace(b"$json_raw = ''",
b"$json_raw = @'\r\n%s\r\n'@" % b_json)
return b_data
def _find_module_utils(module_name, b_module_data, module_path, module_args, task_vars, templar, module_compression, async_timeout, become,
become_method, become_user, become_password, become_flags, environment):
"""
@ -867,53 +930,14 @@ def _find_module_utils(module_name, b_module_data, module_path, module_args, tas
# it can fail in the presence of the UTF8 BOM commonly added by
# Windows text editors
shebang = u'#!powershell'
exec_manifest = dict(
module_entry=to_text(base64.b64encode(b_module_data)),
powershell_modules=dict(),
module_args=module_args,
actions=['exec'],
environment=environment
# create the common exec wrapper payload and set that as the module_data
# bytes
b_module_data = _create_powershell_wrapper(
b_module_data, module_args, environment, async_timeout, become,
become_method, become_user, become_password, become_flags,
scan_dependencies=True
)
exec_manifest['exec'] = to_text(base64.b64encode(to_bytes(leaf_exec)))
if async_timeout > 0:
exec_manifest["actions"].insert(0, 'async_watchdog')
exec_manifest["async_watchdog"] = to_text(base64.b64encode(to_bytes(async_watchdog)))
exec_manifest["actions"].insert(0, 'async_wrapper')
exec_manifest["async_wrapper"] = to_text(base64.b64encode(to_bytes(async_wrapper)))
exec_manifest["async_jid"] = str(random.randint(0, 999999999999))
exec_manifest["async_timeout_sec"] = async_timeout
if become and become_method == 'runas':
exec_manifest["actions"].insert(0, 'become')
exec_manifest["become_user"] = become_user
exec_manifest["become_password"] = become_password
exec_manifest['become_flags'] = become_flags
exec_manifest["become"] = to_text(base64.b64encode(to_bytes(become_wrapper)))
finder = PSModuleDepFinder()
finder.scan_module(b_module_data)
for name, data in finder.modules.items():
b64_data = to_text(base64.b64encode(data))
exec_manifest['powershell_modules'][name] = b64_data
exec_manifest['min_ps_version'] = finder.ps_version
exec_manifest['min_os_version'] = finder.os_version
if finder.become and 'become' not in exec_manifest['actions']:
exec_manifest['actions'].insert(0, 'become')
exec_manifest['become_user'] = 'SYSTEM'
exec_manifest['become_password'] = None
exec_manifest['become_flags'] = None
exec_manifest['become'] = to_text(base64.b64encode(to_bytes(become_wrapper)))
# FUTURE: smuggle this back as a dict instead of serializing here; the connection plugin may need to modify it
module_json = json.dumps(exec_manifest)
b_module_data = exec_wrapper.replace(b"$json_raw = ''", b"$json_raw = @'\r\n%s\r\n'@" % to_bytes(module_json))
elif module_substyle == 'jsonargs':
module_args_json = to_bytes(json.dumps(module_args))

View file

@ -22,9 +22,9 @@ import re
import shlex
from ansible.errors import AnsibleError, AnsibleAction, _AnsibleActionDone, AnsibleActionFail, AnsibleActionSkip
from ansible.executor.module_common import _create_powershell_wrapper
from ansible.module_utils._text import to_bytes, to_native, to_text
from ansible.plugins.action import ActionBase
from ansible.plugins.shell.powershell import exec_wrapper
class ActionModule(ActionBase):
@ -124,13 +124,16 @@ class ActionModule(ActionBase):
script_cmd = self._connection._shell.wrap_for_exec(script_cmd)
exec_data = None
# WinRM requires a special wrapper to work with environment variables
if self._connection.transport == "winrm":
pay = self._connection._create_raw_wrapper_payload(script_cmd,
env_dict)
exec_data = exec_wrapper.replace(b"$json_raw = ''",
b"$json_raw = @'\r\n%s\r\n'@"
% to_bytes(pay))
# PowerShell runs the script in a special wrapper to enable things
# like become and environment args
if self._connection._shell.SHELL_FAMILY == "powershell":
# FIXME: use a more public method to get the exec payload
pc = self._play_context
exec_data = _create_powershell_wrapper(
to_bytes(script_cmd), {}, env_dict, self._task.async_val,
pc.become, pc.become_method, pc.become_user,
pc.become_pass, pc.become_flags, scan_dependencies=False
)
script_cmd = "-"
result.update(self._low_level_execute_command(cmd=script_cmd, in_data=exec_data, sudoable=True, chdir=chdir))

View file

@ -119,7 +119,6 @@ from ansible.module_utils.six.moves.urllib.parse import urlunsplit
from ansible.module_utils._text import to_bytes, to_native, to_text
from ansible.module_utils.six import binary_type
from ansible.plugins.connection import ConnectionBase
from ansible.plugins.shell.powershell import leaf_exec
from ansible.utils.hashing import secure_hash
from ansible.utils.path import makedirs_safe
@ -488,21 +487,6 @@ class Connection(ConnectionBase):
self.shell_id = None
self._connect()
def _create_raw_wrapper_payload(self, cmd, environment=None):
environment = {} if environment is None else environment
payload = {
'module_entry': to_text(base64.b64encode(to_bytes(cmd))),
'powershell_modules': {},
'actions': ['exec'],
'exec': to_text(base64.b64encode(to_bytes(leaf_exec))),
'environment': environment,
'min_ps_version': None,
'min_os_version': None
}
return json.dumps(payload)
def _wrapper_payload_stream(self, payload, buffer_size=200000):
payload_bytes = to_bytes(payload)
byte_count = len(payload_bytes)

View file

@ -987,7 +987,7 @@ $exec_wrapper = {
$output = $entrypoint.Run($payload)
# base64 encode the output so the non-ascii characters are preserved
Write-Output ([System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes((Write-Output $output))))
Write-Output ([System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes((Write-Output ($output | Out-String)))))
} # end exec_wrapper
Function Dump-Error ($excep, $msg=$null) {

View file

@ -0,0 +1,2 @@
whoami.exe
Write-Output "finished"

View file

@ -210,14 +210,14 @@
# - "test_cmd_result is changed"
- name: run test script that takes a boolean parameter
script: test_script_bool.ps1 $true
script: test_script_bool.ps1 $false # use false as that can pick up more errors
register: test_script_bool_result
- name: check that the script ran and the parameter was treated as a boolean
assert:
that:
- "test_script_bool_result.stdout_lines[0] == 'System.Boolean'"
- "test_script_bool_result.stdout_lines[1] == 'True'"
- test_script_bool_result.stdout_lines[0] == 'System.Boolean'
- test_script_bool_result.stdout_lines[1] == 'False'
- name: run test script that uses envvars
script: test_script_with_env.ps1
@ -272,3 +272,16 @@
that:
- test_script_removes_file_check_mode is changed
- remove_file_stat.stat.exists
- name: run test script with become that outputs 2 lines
script: test_script_whoami.ps1
register: test_script_result_become
become: yes
become_user: SYSTEM
become_method: runas
- name: check that the script ran and we get both outputs on new lines
assert:
that:
- test_script_result_become.stdout_lines[0]|lower == 'nt authority\\system'
- test_script_result_become.stdout_lines[1] == 'finished'