def _add_args(self, explanation, args): """ Adds the given args to self._command and displays a caller-supplied explanation of why they were added. """ self._command += args display.vvvvv("SSH: " + explanation + ": (%s)" % ")(".join(args), host=self._play_context.remote_addr)
def _winrm_connect(self): ''' Establish a WinRM connection over HTTP/HTTPS. ''' display.vvv("ESTABLISH WINRM CONNECTION FOR USER: %s on PORT %s TO %s" % (self._winrm_user, self._winrm_port, self._winrm_host), host=self._winrm_host) netloc = '%s:%d' % (self._winrm_host, self._winrm_port) endpoint = urlunsplit((self._winrm_scheme, netloc, self._winrm_path, '', '')) errors = [] for transport in self._winrm_transport: if transport == 'kerberos' and not HAVE_KERBEROS: errors.append('kerberos: the python kerberos library is not installed') continue display.vvvvv('WINRM CONNECT: transport=%s endpoint=%s' % (transport, endpoint), host=self._winrm_host) try: protocol = Protocol(endpoint, transport=transport, **self._winrm_kwargs) protocol.send_message('') return protocol except Exception as e: err_msg = (str(e) or repr(e)).strip() if re.search(r'Operation\s+?timed\s+?out', err_msg, re.I): raise AnsibleError('the connection attempt timed out') m = re.search(r'Code\s+?(\d{3})', err_msg) if m: code = int(m.groups()[0]) if code == 401: err_msg = 'the username/password specified for this server was incorrect' elif code == 411: return protocol errors.append('%s: %s' % (transport, err_msg)) display.vvvvv('WINRM CONNECTION ERROR: %s\n%s' % (err_msg, traceback.format_exc()), host=self._winrm_host) if errors: raise AnsibleError(', '.join(errors)) else: raise AnsibleError('No transport found for WinRM connection')
def path_dwim_relative_stack(self, paths, dirname, source): ''' find one file in first path in stack taking roles into account and adding play basedir as fallback :arg paths: A list of text strings which are the paths to look for the filename in. :arg dirname: A text string representing a directory. The directory is prepended to the source to form the path to search for. :arg source: A text string which is the filename to search for :rtype: A text string :returns: An absolute path to the filename ``source`` ''' b_dirname = to_bytes(dirname) b_source = to_bytes(source) result = None if source is None: display.warning('Invalid request to find a file that matches a "null" value') elif source and (source.startswith('~') or source.startswith(os.path.sep)): # path is absolute, no relative needed, check existence and return source test_path = unfrackpath(b_source) if os.path.exists(to_bytes(test_path, errors='surrogate_or_strict')): result = test_path else: search = [] for path in paths: upath = unfrackpath(path) b_upath = to_bytes(upath, errors='surrogate_or_strict') b_mydir = os.path.dirname(b_upath) # if path is in role and 'tasks' not there already, add it into the search if b_upath.endswith(b'tasks') and os.path.exists(os.path.join(b_upath, b'main.yml')) \ or os.path.exists(os.path.join(b_upath, b'tasks/main.yml')) \ or os.path.exists(os.path.join(b_mydir, b'tasks/main.yml')): if b_mydir.endswith(b'tasks'): search.append(os.path.join(os.path.dirname(b_mydir), b_dirname, b_source)) search.append(os.path.join(b_mydir, b_source)) else: # don't add dirname if user already is using it in source if b_source.split(b'/')[0] == b_dirname: search.append(os.path.join(b_upath, b_source)) else: search.append(os.path.join(b_upath, b_dirname, b_source)) search.append(os.path.join(b_upath, b'tasks', b_source)) elif b_dirname not in b_source.split(b'/'): # don't add dirname if user already is using it in source search.append(os.path.join(b_upath, b_dirname, b_source)) search.append(os.path.join(b_upath, b_source)) # always append basedir as last resort search.append(os.path.join(to_bytes(self.get_basedir()), b_dirname, b_source)) search.append(os.path.join(to_bytes(self.get_basedir()), b_source)) display.debug(u'search_path:\n\t%s' % to_text(b'\n\t'.join(search))) for b_candidate in search: display.vvvvv(u'looking for "%s" at "%s"' % (source, to_text(b_candidate))) if os.path.exists(b_candidate): result = to_text(b_candidate) break return result
def close(self): if self.protocol and self.shell_id: display.vvvvv('WINRM CLOSE SHELL: %s' % self.shell_id, host=self._winrm_host) self.protocol.close_shell(self.shell_id) self.shell_id = None self.protocol = None self._connected = False
def _connect(self): """ no persistent connection is being maintained, mount container's filesystem so we can easily access it """ super(Connection, self)._connect() rc, self._mount_point, stderr = self._buildah("mount") self._mount_point = self._mount_point.strip() display.vvvvv("MOUNTPOINT %s RC %s STDERR %r" % (self._mount_point, rc, stderr)) self._connected = True
def _kerb_auth(self, principal, password): if password is None: password = "" self._kerb_ccache = tempfile.NamedTemporaryFile() display.vvvvv("creating Kerberos CC at %s" % self._kerb_ccache.name) krb5ccname = "FILE:%s" % self._kerb_ccache.name os.environ["KRB5CCNAME"] = krb5ccname krb5env = dict(KRB5CCNAME=krb5ccname) # stores various flags to call with kinit, we currently only use this # to set -f so we can get a forward-able ticket (cred delegation) kinit_flags = [] if boolean(self.get_option('_extras').get('ansible_winrm_kerberos_delegation', False)): kinit_flags.append('-f') kinit_cmdline = [self._kinit_cmd] kinit_cmdline.extend(kinit_flags) kinit_cmdline.append(principal) # pexpect runs the process in its own pty so it can correctly send # the password as input even on MacOS which blocks subprocess from # doing so. Unfortunately it is not available on the built in Python # so we can only use it if someone has installed it if HAS_PEXPECT: kinit_cmdline = " ".join(kinit_cmdline) password = to_text(password, encoding='utf-8', errors='surrogate_or_strict') display.vvvv("calling kinit with pexpect for principal %s" % principal) events = { ".*:": password + "\n" } # technically this is the stdout but to match subprocess we will # call it stderr stderr, rc = pexpect.run(kinit_cmdline, withexitstatus=True, events=events, env=krb5env, timeout=60) else: password = to_bytes(password, encoding='utf-8', errors='surrogate_or_strict') display.vvvv("calling kinit with subprocess for principal %s" % principal) p = subprocess.Popen(kinit_cmdline, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=krb5env) stdout, stderr = p.communicate(password + b'\n') rc = p.returncode != 0 if rc != 0: raise AnsibleConnectionFailure("Kerberos auth failure: %s" % to_native(stderr.strip())) display.vvvvv("kinit succeeded for principal %s" % principal)
def exec_command(self, cmd, in_data=None, sudoable=False): """ run specified command in a running OCI container using buildah """ super(Connection, self).exec_command(cmd, in_data=in_data, sudoable=sudoable) # shlex.split has a bug with text strings on Python-2.6 and can only handle text strings on Python-3 cmd_args_list = shlex.split(to_native(cmd, errors='surrogate_or_strict')) rc, stdout, stderr = self._buildah("run", cmd_args_list) display.vvvvv("STDOUT %r STDERR %r" % (stderr, stderr)) return rc, stdout, stderr
def _winrm_exec(self, command, args=(), from_exec=False, stdin_iterator=None): if not self.protocol: self.protocol = self._winrm_connect() self._connected = True if from_exec: display.vvvvv("WINRM EXEC %r %r" % (command, args), host=self._winrm_host) else: display.vvvvvv("WINRM EXEC %r %r" % (command, args), host=self._winrm_host) command_id = None try: stdin_push_failed = False command_id = self.protocol.run_command(self.shell_id, to_bytes(command), map(to_bytes, args), console_mode_stdin=(stdin_iterator is None)) # TODO: try/except around this, so we can get/return the command result on a broken pipe or other failure (probably more useful than the 500 that # comes from this) try: if stdin_iterator: for (data, is_last) in stdin_iterator: self._winrm_send_input(self.protocol, self.shell_id, command_id, data, eof=is_last) except Exception as ex: from traceback import format_exc display.warning("FATAL ERROR DURING FILE TRANSFER: %s" % format_exc(ex)) stdin_push_failed = True if stdin_push_failed: raise AnsibleError('winrm send_input failed') # NB: this can hang if the receiver is still running (eg, network failed a Send request but the server's still happy). # FUTURE: Consider adding pywinrm status check/abort operations to see if the target is still running after a failure. resptuple = self.protocol.get_command_output(self.shell_id, command_id) # ensure stdout/stderr are text for py3 # FUTURE: this should probably be done internally by pywinrm response = Response(tuple(to_text(v) if isinstance(v, binary_type) else v for v in resptuple)) # TODO: check result from response and set stdin_push_failed if we have nonzero if from_exec: display.vvvvv('WINRM RESULT %r' % to_text(response), host=self._winrm_host) else: display.vvvvvv('WINRM RESULT %r' % to_text(response), host=self._winrm_host) display.vvvvvv('WINRM STDOUT %s' % to_text(response.std_out), host=self._winrm_host) display.vvvvvv('WINRM STDERR %s' % to_text(response.std_err), host=self._winrm_host) if stdin_push_failed: raise AnsibleError('winrm send_input failed; \nstdout: %s\nstderr %s' % (response.std_out, response.std_err)) return response finally: if command_id: self.protocol.cleanup_command(self.shell_id, command_id)
def _add_args(self, b_command, b_args, explanation): """ Adds arguments to the ssh command and displays a caller-supplied explanation of why. :arg b_command: A list containing the command to add the new arguments to. This list will be modified by this method. :arg b_args: An iterable of new arguments to add. This iterable is used more than once so it must be persistent (ie: a list is okay but a StringIO would not) :arg explanation: A text string containing explaining why the arguments were added. It will be displayed with a high enough verbosity. .. note:: This function does its work via side-effect. The b_command list has the new arguments appended. """ display.vvvvv(u'SSH: %s: (%s)' % (explanation, ')('.join(to_text(a) for a in b_args)), host=self._play_context.remote_addr) b_command += b_args
def metadata(self): """ Returns role metadata """ if self._metadata is None: meta_path = os.path.join(self.path, self.META_MAIN) if os.path.isfile(meta_path): try: f = open(meta_path, 'r') self._metadata = yaml.safe_load(f) except: display.vvvvv("Unable to load metadata for %s" % self.name) return False finally: f.close() return self._metadata
def install_info(self): """ Returns role install info """ if self._install_info is None: info_path = os.path.join(self.path, self.META_INSTALL) if os.path.isfile(info_path): try: f = open(info_path, 'r') self._install_info = yaml.safe_load(f) except: display.vvvvv("Unable to load Galaxy install info for %s" % self.name) return False finally: f.close() return self._install_info
def _connect(self): if not HAS_PYPSRP: raise AnsibleError("pypsrp or dependencies are not installed: %s" % to_native(PYPSRP_IMP_ERR)) super(Connection, self)._connect() self._build_kwargs() display.vvv("ESTABLISH PSRP CONNECTION FOR USER: %s ON PORT %s TO %s" % (self._psrp_user, self._psrp_port, self._psrp_host), host=self._psrp_host) if not self.runspace: connection = WSMan(**self._psrp_conn_kwargs) # create our psuedo host to capture the exit code and host output host_ui = PSHostUserInterface() self.host = PSHost(None, None, False, "Ansible PSRP Host", None, host_ui, None) self.runspace = RunspacePool( connection, host=self.host, configuration_name=self._psrp_configuration_name ) display.vvvvv( "PSRP OPEN RUNSPACE: auth=%s configuration=%s endpoint=%s" % (self._psrp_auth, self._psrp_configuration_name, connection.transport.endpoint), host=self._psrp_host ) try: self.runspace.open() except AuthenticationError as e: raise AnsibleConnectionFailure("failed to authenticate with " "the server: %s" % to_native(e)) except WinRMError as e: raise AnsibleConnectionFailure( "psrp connection failure during runspace open: %s" % to_native(e) ) except (ConnectionError, ConnectTimeout) as e: raise AnsibleConnectionFailure( "Failed to connect to the host via PSRP: %s" % to_native(e) ) self._connected = True return self
def _kerb_auth(self, principal, password): if password is None: password = "" self._kerb_ccache = tempfile.NamedTemporaryFile() display.vvvvv("creating Kerberos CC at %s" % self._kerb_ccache.name) krb5ccname = "FILE:%s" % self._kerb_ccache.name os.environ["KRB5CCNAME"] = krb5ccname krb5env = dict(KRB5CCNAME=krb5ccname) # pexpect runs the process in its own pty so it can correctly send # the password as input even on MacOS which blocks subprocess from # doing so. Unfortunately it is not available on the built in Python # so we can only use it if someone has installed it if HAS_PEXPECT: kinit_cmdline = "%s %s" % (self._kinit_cmd, principal) password = to_text(password, encoding='utf-8', errors='surrogate_or_strict') display.vvvv("calling kinit with pexpect for principal %s" % principal) events = { ".*:": password + "\n" } # technically this is the stdout but to match subprocess we wil call # it stderr stderr, rc = pexpect.run(kinit_cmdline, withexitstatus=True, events=events, env=krb5env, timeout=60) else: kinit_cmdline = [self._kinit_cmd, principal] password = to_bytes(password, encoding='utf-8', errors='surrogate_or_strict') display.vvvv("calling kinit with subprocess for principal %s" % principal) p = subprocess.Popen(kinit_cmdline, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=krb5env) stdout, stderr = p.communicate(password + b'\n') rc = p.returncode != 0 if rc != 0: raise AnsibleConnectionFailure("Kerberos auth failure: %s" % stderr.strip()) display.vvvvv("kinit succeeded for principal %s" % principal)
def path_dwim_relative_stack(self, paths, dirname, source): ''' find one file in first path in stack taking roles into account and adding play basedir as fallback ''' result = None if not source: display.warning('Invalid request to find a file that matches an empty string or "null" value') elif source.startswith('~') or source.startswith(os.path.sep): # path is absolute, no relative needed, check existence and return source test_path = to_bytes(unfrackpath(source),errors='strict') if os.path.exists(test_path): result = test_path else: search = [] for path in paths: upath = unfrackpath(path) mydir = os.path.dirname(upath) # if path is in role and 'tasks' not there already, add it into the search if upath.endswith('tasks') and os.path.exists(to_bytes(os.path.join(upath,'main.yml'), errors='strict')) \ or os.path.exists(to_bytes(os.path.join(upath,'tasks/main.yml'), errors='strict')) \ or os.path.exists(to_bytes(os.path.join(os.path.dirname(upath),'tasks/main.yml'), errors='strict')): if mydir.endswith('tasks'): search.append(os.path.join(os.path.dirname(mydir), dirname, source)) search.append(os.path.join(mydir, source)) else: search.append(os.path.join(upath, dirname, source)) search.append(os.path.join(upath, 'tasks', source)) elif dirname not in source.split('/'): # don't add dirname if user already is using it in source search.append(os.path.join(upath, dirname, source)) search.append(os.path.join(upath, source)) # always append basedir as last resort search.append(os.path.join(self.get_basedir(), dirname, source)) search.append(os.path.join(self.get_basedir(), source)) display.debug('search_path:\n\t' + '\n\t'.join(search)) for candidate in search: display.vvvvv('looking for "%s" at "%s"' % (source, candidate)) if os.path.exists(to_bytes(candidate, errors='strict')): result = candidate break return result
def path_dwim_relative_stack(self, paths, dirname, source): ''' find one file in first path in stack taking roles into account and adding play basedir as fallback ''' result = None if source.startswith('~') or source.startswith(os.path.sep): # path is absolute, no relative needed, check existence and return source test_path = to_bytes(unfrackpath(source), errors='strict') if os.path.exists(test_path): result = test_path else: search = [] for path in paths: upath = unfrackpath(path) mydir = os.path.dirname(upath) # if path is in role and 'tasks' not there already, add it into the search if upath.endswith('tasks') and os.path.exists(to_bytes(os.path.join(upath,'main.yml'), errors='strict')) \ or os.path.exists(to_bytes(os.path.join(upath,'tasks/main.yml'), errors='strict')) \ or os.path.exists(to_bytes(os.path.join(os.path.dirname(upath),'tasks/main.yml'), errors='strict')): if mydir.endswith('tasks'): search.append( os.path.join(os.path.dirname(mydir), dirname, source)) search.append(os.path.join(mydir, source)) else: search.append(os.path.join(upath, dirname, source)) search.append(os.path.join(upath, 'tasks', source)) elif dirname not in source.split('/'): # don't add dirname if user already is using it in source search.append(os.path.join(upath, dirname, source)) search.append(os.path.join(upath, source)) # always append basedir as last resort search.append(os.path.join(self.get_basedir(), dirname, source)) search.append(os.path.join(self.get_basedir(), source)) display.debug('search_path:\n\t' + '\n\t'.join(search)) for candidate in search: display.vvvvv('looking for "%s" at "%s"' % (source, candidate)) if os.path.exists(to_bytes(candidate, errors='strict')): result = candidate break return result
def _winrm_connect(self): ''' Establish a WinRM connection over HTTP/HTTPS. ''' display.vvv( "ESTABLISH WINRM CONNECTION FOR USER: %s on PORT %s TO %s" % (self._winrm_user, self._winrm_port, self._winrm_host), host=self._winrm_host) netloc = '%s:%d' % (self._winrm_host, self._winrm_port) endpoint = urlunsplit( (self._winrm_scheme, netloc, self._winrm_path, '', '')) errors = [] for transport in self._winrm_transport: if transport == 'kerberos' and not HAVE_KERBEROS: errors.append( 'kerberos: the python kerberos library is not installed') continue display.vvvvv('WINRM CONNECT: transport=%s endpoint=%s' % (transport, endpoint), host=self._winrm_host) try: protocol = Protocol(endpoint, transport=transport, **self._winrm_kwargs) protocol.send_message('') return protocol except Exception as e: err_msg = (str(e) or repr(e)).strip() if re.search(r'Operation\s+?timed\s+?out', err_msg, re.I): raise AnsibleError('the connection attempt timed out') m = re.search(r'Code\s+?(\d{3})', err_msg) if m: code = int(m.groups()[0]) if code == 401: err_msg = 'the username/password specified for this server was incorrect' elif code == 411: return protocol errors.append('%s: %s' % (transport, err_msg)) display.vvvvv('WINRM CONNECTION ERROR: %s\n%s' % (err_msg, traceback.format_exc()), host=self._winrm_host) if errors: raise AnsibleError(', '.join(errors)) else: raise AnsibleError('No transport found for WinRM connection')
def get_host_vars(self, host, vault_password=None): """ Get host specific variables. """ resulting_host_vars = {} var_files = [] for host_var_folder in self.host_vars_folders: var_files.extend(vars_files_loading(host_var_folder, host.name)) _dataloader = DataLoader() _dataloader.set_vault_password(vault_password) for filename in var_files: display.vvvvv("Hostname {}: Loading var file {}".format( host.name, filename)) data = _dataloader.load_from_file(filename) if data is not None: resulting_host_vars = merge_hash(resulting_host_vars, data) return resulting_host_vars
def get_group_vars(self, group, vault_password=None): """ Get group specific variables. """ resulting_group_vars = {} var_files = [] for grp_var_folder in self.grp_vars_folders: var_files.extend(vars_files_loading(grp_var_folder, group.name)) _dataloader = DataLoader() _dataloader.set_vault_password(vault_password) for filename in var_files: display.vvvvv("Group {}: Loading var file {}".format( group.name, filename)) data = _dataloader.load_from_file(filename) if data is not None: resulting_group_vars = merge_hash(resulting_group_vars, data) return resulting_group_vars
def encrypt(self, plaintext, secret=None, vault_id=None): """Vault encrypt a piece of data. :arg plaintext: a text or byte string to encrypt. :returns: a utf-8 encoded byte str of encrypted data. The string contains a header identifying this as vault encrypted data and formatted to newline terminated lines of 80 characters. This is suitable for dumping as is to a vault file. If the string passed in is a text string, it will be encoded to UTF-8 before encryption. """ if secret is None: if self.secrets: dummy, secret = match_encrypt_secret(self.secrets) else: raise AnsibleVaultError( "A vault password must be specified to encrypt data") b_plaintext = to_bytes(plaintext, errors='surrogate_or_strict') if is_encrypted(b_plaintext): raise AnsibleError("input is already encrypted") if not self.cipher_name or self.cipher_name not in CIPHER_WRITE_WHITELIST: self.cipher_name = u"AES256" try: this_cipher = CIPHER_MAPPING[self.cipher_name]() except KeyError: raise AnsibleError(u"{0} cipher could not be found".format( self.cipher_name)) # encrypt data display.vvvvv('Encrypting with vault secret %s' % secret) b_ciphertext = this_cipher.encrypt(b_plaintext, secret) # format the data for output to the file b_vaulttext = format_vaulttext_envelope(b_ciphertext, self.cipher_name, vault_id=vault_id) return b_vaulttext
def encrypt(self, plaintext, secret=None, vault_id=None): """Vault encrypt a piece of data. :arg plaintext: a text or byte string to encrypt. :returns: a utf-8 encoded byte str of encrypted data. The string contains a header identifying this as vault encrypted data and formatted to newline terminated lines of 80 characters. This is suitable for dumping as is to a vault file. If the string passed in is a text string, it will be encoded to UTF-8 before encryption. """ if secret is None: if self.secrets: dummy, secret = match_encrypt_secret(self.secrets) else: raise AnsibleVaultError("A vault password must be specified to encrypt data") b_plaintext = to_bytes(plaintext, errors='surrogate_or_strict') if is_encrypted(b_plaintext): raise AnsibleError("input is already encrypted") if not self.cipher_name or self.cipher_name not in CIPHER_WRITE_WHITELIST: self.cipher_name = u"AES256" try: this_cipher = CIPHER_MAPPING[self.cipher_name]() except KeyError: raise AnsibleError(u"{0} cipher could not be found".format(self.cipher_name)) # encrypt data display.vvvvv('Encrypting with vault secret %s' % secret) b_ciphertext = this_cipher.encrypt(b_plaintext, secret) # format the data for output to the file b_vaulttext = format_vaulttext_envelope(b_ciphertext, self.cipher_name, vault_id=vault_id) return b_vaulttext
def remove_github_token(self): ''' If for some reason an ansible-galaxy token was left from a prior login, remove it. We cannot retrieve the token after creation, so we are forced to create a new one. ''' try: tokens = json.load(open_url(self.GITHUB_AUTH, url_username=self.github_username, url_password=self.github_password, force_basic_auth=True,)) except HTTPError as e: res = json.load(e) raise AnsibleError(res['message']) for token in tokens: if token['note'] == 'ansible-galaxy login': display.vvvvv('removing token: %s' % token['token_last_eight']) try: open_url('https://api.github.com/authorizations/%d' % token['id'], url_username=self.github_username, url_password=self.github_password, method='DELETE', force_basic_auth=True,) except HTTPError as e: res = json.load(e) raise AnsibleError(res['message'])
def _kerb_auth(self, principal, password): if password is None: password = "" self._kerb_ccache = tempfile.NamedTemporaryFile() display.vvvvv("creating Kerberos CC at %s" % self._kerb_ccache.name) krb5ccname = "FILE:%s" % self._kerb_ccache.name krbenv = dict(KRB5CCNAME=krb5ccname) os.environ["KRB5CCNAME"] = krb5ccname kinit_cmdline = [self._kinit_cmd, principal] display.vvvvv("calling kinit for principal %s" % principal) p = subprocess.Popen(kinit_cmdline, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=krbenv) # TODO: unicode/py3 stdout, stderr = p.communicate(password + b'\n') if p.returncode != 0: raise AnsibleConnectionFailure("Kerberos auth failure: %s" % stderr.strip()) display.vvvvv("kinit succeeded for principal %s" % principal)
def __init__(self, galaxy, api_server): self.galaxy = galaxy try: urlparse(api_server, scheme='https') except: raise AnsibleError("Invalid server API url passed: %s" % api_server) server_version = self.get_server_api_version('%s/api/' % (api_server)) if not server_version: raise AnsibleError("Could not retrieve server API version: %s" % api_server) if server_version in self.SUPPORTED_VERSIONS: self.baseurl = '%s/api/%s' % (api_server, server_version) self.version = server_version # for future use display.vvvvv("Base API: %s" % self.baseurl) else: raise AnsibleError("Unsupported Galaxy server API version: %s" % server_version)
def _winrm_exec(self, command, args=(), from_exec=False, stdin_iterator=None): if from_exec: display.vvvvv("WINRM EXEC %r %r" % (command, args), host=self._winrm_host) else: display.vvvvvv("WINRM EXEC %r %r" % (command, args), host=self._winrm_host) if not self.protocol: self.protocol = self._winrm_connect() if not self.shell_id: self.shell_id = self.protocol.open_shell(codepage=65001) # UTF-8 command_id = None try: stdin_push_failed = False command_id = self.protocol.run_command(self.shell_id, to_bytes(command), map(to_bytes, args), console_mode_stdin=(stdin_iterator == None)) # TODO: try/except around this, so we can get/return the command result on a broken pipe or other failure (probably more useful than the 500 that comes from this) try: if stdin_iterator: for (data, is_last) in stdin_iterator: self._winrm_send_input(self.protocol, self.shell_id, command_id, data, eof=is_last) except: stdin_push_failed = True # NB: this could hang if the receiver is still running (eg, network failed a Send request but the server's still happy). # FUTURE: Consider adding pywinrm status check/abort operations to see if the target is still running after a failure. response = Response(self.protocol.get_command_output(self.shell_id, command_id)) if from_exec: display.vvvvv('WINRM RESULT %r' % to_unicode(response), host=self._winrm_host) else: display.vvvvvv('WINRM RESULT %r' % to_unicode(response), host=self._winrm_host) display.vvvvvv('WINRM STDOUT %s' % to_unicode(response.std_out), host=self._winrm_host) display.vvvvvv('WINRM STDERR %s' % to_unicode(response.std_err), host=self._winrm_host) if stdin_push_failed: raise AnsibleError('winrm send_input failed; \nstdout: %s\nstderr %s' % (response.std_out, response.std_err)) return response finally: if command_id: self.protocol.cleanup_command(self.shell_id, command_id)
def _winrm_connect(self): ''' Establish a WinRM connection over HTTP/HTTPS. ''' display.vvv("ESTABLISH WINRM CONNECTION FOR USER: %s on PORT %s TO %s" % (self._winrm_user, self._winrm_port, self._winrm_host), host=self._winrm_host) winrm_host = self._winrm_host if HAS_IPADDRESS: display.vvvv("checking if winrm_host %s is an IPv6 address" % winrm_host) try: ipaddress.IPv6Address(winrm_host) except ipaddress.AddressValueError: pass else: winrm_host = "[%s]" % winrm_host netloc = '%s:%d' % (winrm_host, self._winrm_port) endpoint = urlunsplit((self._winrm_scheme, netloc, self._winrm_path, '', '')) errors = [] for transport in self._winrm_transport: if transport == 'kerberos': if not HAVE_KERBEROS: errors.append('kerberos: the python kerberos library is not installed') continue if self._kerb_managed: self._kerb_auth(self._winrm_user, self._winrm_pass) display.vvvvv('WINRM CONNECT: transport=%s endpoint=%s' % (transport, endpoint), host=self._winrm_host) try: winrm_kwargs = self._winrm_kwargs.copy() if self._winrm_connection_timeout: winrm_kwargs['operation_timeout_sec'] = self._winrm_connection_timeout winrm_kwargs['read_timeout_sec'] = self._winrm_connection_timeout + 1 protocol = Protocol(endpoint, transport=transport, **winrm_kwargs) # open the shell from connect so we know we're able to talk to the server if not self.shell_id: self.shell_id = protocol.open_shell(codepage=65001) # UTF-8 display.vvvvv('WINRM OPEN SHELL: %s' % self.shell_id, host=self._winrm_host) return protocol except Exception as e: err_msg = to_text(e).strip() if re.search(to_text(r'Operation\s+?timed\s+?out'), err_msg, re.I): raise AnsibleError('the connection attempt timed out') m = re.search(to_text(r'Code\s+?(\d{3})'), err_msg) if m: code = int(m.groups()[0]) if code == 401: err_msg = 'the specified credentials were rejected by the server' elif code == 411: return protocol errors.append(u'%s: %s' % (transport, err_msg)) display.vvvvv(u'WINRM CONNECTION ERROR: %s\n%s' % (err_msg, to_text(traceback.format_exc())), host=self._winrm_host) if errors: raise AnsibleConnectionFailure(', '.join(map(to_native, errors))) else: raise AnsibleError('No transport found for WinRM connection')
def _winrm_connect(self): ''' Establish a WinRM connection over HTTP/HTTPS. ''' display.vvv( "ESTABLISH WINRM CONNECTION FOR USER: %s on PORT %s TO %s" % (self._winrm_user, self._winrm_port, self._winrm_host), host=self._winrm_host) netloc = '%s:%d' % (self._winrm_host, self._winrm_port) endpoint = urlunsplit( (self._winrm_scheme, netloc, self._winrm_path, '', '')) errors = [] for transport in self._winrm_transport: if transport == 'kerberos' and not HAVE_KERBEROS: errors.append( 'kerberos: the python kerberos library is not installed') continue display.vvvvv('WINRM CONNECT: transport=%s endpoint=%s' % (transport, endpoint), host=self._winrm_host) try: protocol = Protocol(endpoint, transport=transport, **self._winrm_kwargs) # send keepalive message to ensure we're awake # TODO: is this necessary? # protocol.send_message(xmltodict.unparse(rq)) if not self.shell_id: self.shell_id = protocol.open_shell( codepage=65001) # UTF-8 display.vvvvv('WINRM OPEN SHELL: %s' % self.shell_id, host=self._winrm_host) return protocol except Exception as e: err_msg = to_unicode(e).strip() if re.search(to_unicode(r'Operation\s+?timed\s+?out'), err_msg, re.I): raise AnsibleError('the connection attempt timed out') m = re.search(to_unicode(r'Code\s+?(\d{3})'), err_msg) if m: code = int(m.groups()[0]) if code == 401: err_msg = 'the username/password specified for this server was incorrect' elif code == 411: return protocol errors.append(u'%s: %s' % (transport, err_msg)) display.vvvvv(u'WINRM CONNECTION ERROR: %s\n%s' % (err_msg, to_unicode(traceback.format_exc())), host=self._winrm_host) if errors: raise AnsibleConnectionFailure(', '.join(map(to_str, errors))) else: raise AnsibleError('No transport found for WinRM connection')
def _winrm_connect(self): ''' Establish a WinRM connection over HTTP/HTTPS. ''' display.vvv("ESTABLISH WINRM CONNECTION FOR USER: %s on PORT %s TO %s" % (self._winrm_user, self._winrm_port, self._winrm_host), host=self._winrm_host) netloc = '%s:%d' % (self._winrm_host, self._winrm_port) endpoint = urlunsplit((self._winrm_scheme, netloc, self._winrm_path, '', '')) errors = [] for transport in self._winrm_transport: if transport == 'kerberos' and not HAVE_KERBEROS: errors.append('kerberos: the python kerberos library is not installed') continue display.vvvvv('WINRM CONNECT: transport=%s endpoint=%s' % (transport, endpoint), host=self._winrm_host) try: protocol = Protocol(endpoint, transport=transport, **self._winrm_kwargs) # send keepalive message to ensure we're awake # TODO: is this necessary? # protocol.send_message(xmltodict.unparse(rq)) if not self.shell_id: self.shell_id = protocol.open_shell(codepage=65001) # UTF-8 display.vvvvv('WINRM OPEN SHELL: %s' % self.shell_id, host=self._winrm_host) return protocol except Exception as e: err_msg = to_unicode(e).strip() if re.search(to_unicode(r'Operation\s+?timed\s+?out'), err_msg, re.I): raise AnsibleError('the connection attempt timed out') m = re.search(to_unicode(r'Code\s+?(\d{3})'), err_msg) if m: code = int(m.groups()[0]) if code == 401: err_msg = 'the username/password specified for this server was incorrect' elif code == 411: return protocol errors.append(u'%s: %s' % (transport, err_msg)) display.vvvvv(u'WINRM CONNECTION ERROR: %s\n%s' % (err_msg, to_unicode(traceback.format_exc())), host=self._winrm_host) if errors: raise AnsibleConnectionFailure(', '.join(map(to_str, errors))) else: raise AnsibleError('No transport found for WinRM connection')
def _winrm_exec(self, command, args=(), from_exec=False, stdin_iterator=None): if not self.protocol: self.protocol = self._winrm_connect() self._connected = True if not self.shell_id: self.shell_id = self.protocol.open_shell(codepage=65001) # UTF-8 display.vvvvv('WINRM OPEN SHELL: %s' % self.shell_id, host=self._winrm_host) if from_exec: display.vvvvv("WINRM EXEC %r %r" % (command, args), host=self._winrm_host) else: display.vvvvvv("WINRM EXEC %r %r" % (command, args), host=self._winrm_host) command_id = None try: stdin_push_failed = False command_id = self.protocol.run_command(self.shell_id, to_bytes(command), map(to_bytes, args), console_mode_stdin=(stdin_iterator == None)) # TODO: try/except around this, so we can get/return the command result on a broken pipe or other failure (probably more useful than the 500 that comes from this) try: if stdin_iterator: for (data, is_last) in stdin_iterator: self._winrm_send_input(self.protocol, self.shell_id, command_id, data, eof=is_last) except: stdin_push_failed = True # NB: this could hang if the receiver is still running (eg, network failed a Send request but the server's still happy). # FUTURE: Consider adding pywinrm status check/abort operations to see if the target is still running after a failure. response = Response(self.protocol.get_command_output(self.shell_id, command_id)) if from_exec: display.vvvvv('WINRM RESULT %r' % to_unicode(response), host=self._winrm_host) else: display.vvvvvv('WINRM RESULT %r' % to_unicode(response), host=self._winrm_host) display.vvvvvv('WINRM STDOUT %s' % to_unicode(response.std_out), host=self._winrm_host) display.vvvvvv('WINRM STDERR %s' % to_unicode(response.std_err), host=self._winrm_host) if stdin_push_failed: raise AnsibleError('winrm send_input failed; \nstdout: %s\nstderr %s' % (response.std_out, response.std_err)) return response finally: if command_id: self.protocol.cleanup_command(self.shell_id, command_id)
def run(self, basepath, variables=None, **kwargs): for ssh_soc in chain.from_iterable(map(lambda x: glob.glob(x), basepath)): os.environ['SSH_AUTH_SOCK'] = ssh_soc display.vvvvv('searching for socket in %s' % os.environ['SSH_AUTH_SOCK']) a = paramiko.Agent() display.vv('keys found in socket %s: %s' % (os.environ['SSH_AUTH_SOCK'], a.get_keys())) if a.get_keys() != (): if kwargs.has_key('keyname'): keyname = kwargs['keyname'] keys = map(lambda x: x.get_base64(), a.get_keys()) display.vvvvv('checking for key %s' % keyname) display.vvvvv('available keys:\n\t%s' % '\n\t'.join(keys)) if any([keyname in keys, keyname in map(lambda x: x[:len(keyname)], keys), keyname in map(lambda x: x[len(x) - len(keyname):], keys),]): display.vv('key %s found' % keyname) return [ ssh_soc ] else: display.vv('socket %s found' % ssh_soc) return [ ssh_soc ] raise AnsibleError('couldnt find any ssh Agent socket in path %s' % basepath)
def reset(self): display.vvvvv("PSRP: Reset Connection", host=self._psrp_host) self.runspace = None self._connect()
def setup_vault_secrets(loader, vault_ids, vault_password_files=None, ask_vault_pass=None, create_new_password=False, auto_prompt=True): # list of tuples vault_secrets = [] # Depending on the vault_id value (including how --ask-vault-pass / --vault-password-file create a vault_id) # we need to show different prompts. This is for compat with older Towers that expect a # certain vault password prompt format, so 'promp_ask_vault_pass' vault_id gets the old format. prompt_formats = {} # If there are configured default vault identities, they are considered 'first' # so we prepend them to vault_ids (from cli) here vault_password_files = vault_password_files or [] if C.DEFAULT_VAULT_PASSWORD_FILE: vault_password_files.append(C.DEFAULT_VAULT_PASSWORD_FILE) if create_new_password: prompt_formats['prompt'] = ['New vault password (%(vault_id)s): ', 'Confirm vew vault password (%(vault_id)s): '] # 2.3 format prompts for --ask-vault-pass prompt_formats['prompt_ask_vault_pass'] = ['New Vault password: '******'Confirm New Vault password: '******'prompt'] = ['Vault password (%(vault_id)s): '] # The format when we use just --ask-vault-pass needs to match 'Vault password:\s*?$' prompt_formats['prompt_ask_vault_pass'] = ['Vault password: '******'prompt', 'prompt_ask_vault_pass']: # --vault-id some_name@prompt_ask_vault_pass --vault-id other_name@prompt_ask_vault_pass will be a little # confusing since it will use the old format without the vault id in the prompt built_vault_id = vault_id_name or C.DEFAULT_VAULT_IDENTITY # choose the prompt based on --vault-id=prompt or --ask-vault-pass. --ask-vault-pass # always gets the old format for Tower compatibility. # ie, we used --ask-vault-pass, so we need to use the old vault password prompt # format since Tower needs to match on that format. prompted_vault_secret = PromptVaultSecret(prompt_formats=prompt_formats[vault_id_value], vault_id=built_vault_id) # a empty or invalid password from the prompt will warn and continue to the next # without erroring globablly try: prompted_vault_secret.load() except AnsibleError as exc: display.warning('Error in vault password prompt (%s): %s' % (vault_id_name, exc)) raise vault_secrets.append((built_vault_id, prompted_vault_secret)) # update loader with new secrets incrementally, so we can load a vault password # that is encrypted with a vault secret provided earlier loader.set_vault_secrets(vault_secrets) continue # assuming anything else is a password file display.vvvvv('Reading vault password file: %s' % vault_id_value) # read vault_pass from a file file_vault_secret = get_file_vault_secret(filename=vault_id_value, vault_id=vault_id_name, loader=loader) # an invalid password file will error globally try: file_vault_secret.load() except AnsibleError as exc: display.warning('Error in vault password file loading (%s): %s' % (vault_id_name, exc)) raise if vault_id_name: vault_secrets.append((vault_id_name, file_vault_secret)) else: vault_secrets.append((C.DEFAULT_VAULT_IDENTITY, file_vault_secret)) # update loader with as-yet-known vault secrets loader.set_vault_secrets(vault_secrets) return vault_secrets
def recursive_finder(name, data, py_module_names, py_module_cache, zf): """ Using ModuleDepFinder, make sure we have all of the module_utils files that the module its module_utils files needs. """ # Parse the module and find the imports of ansible.module_utils tree = ast.parse(data) finder = ModuleDepFinder() finder.visit(tree) # # Determine what imports that we've found are modules (vs class, function. # variable names) for packages # normalized_modules = set() # Loop through the imports that we've found to normalize them # Exclude paths that match with paths we've already processed # (Have to exclude them a second time once the paths are processed) module_utils_paths = [p for p in module_utils_loader._get_paths(subdirs=False) if os.path.isdir(p)] module_utils_paths.append(_MODULE_UTILS_PATH) for py_module_name in finder.submodules.difference(py_module_names): module_info = None if py_module_name[0] == 'six': # Special case the python six library because it messes up the # import process in an incompatible way module_info = imp.find_module('six', module_utils_paths) py_module_name = ('six',) idx = 0 elif py_module_name[0] == '_six': # Special case the python six library because it messes up the # import process in an incompatible way module_info = imp.find_module('_six', [os.path.join(p, 'six') for p in module_utils_paths]) py_module_name = ('six', '_six') idx = 0 else: # Check whether either the last or the second to last identifier is # a module name for idx in (1, 2): if len(py_module_name) < idx: break try: module_info = imp.find_module(py_module_name[-idx], [os.path.join(p, *py_module_name[:-idx]) for p in module_utils_paths]) break except ImportError: continue # Could not find the module. Construct a helpful error message. if module_info is None: msg = ['Could not find imported module support code for %s. Looked for' % (name,)] if idx == 2: msg.append('either %s.py or %s.py' % (py_module_name[-1], py_module_name[-2])) else: msg.append(py_module_name[-1]) raise AnsibleError(' '.join(msg)) # Found a byte compiled file rather than source. We cannot send byte # compiled over the wire as the python version might be different. # imp.find_module seems to prefer to return source packages so we just # error out if imp.find_module returns byte compiled files (This is # fragile as it depends on undocumented imp.find_module behaviour) if module_info[2][2] not in (imp.PY_SOURCE, imp.PKG_DIRECTORY): msg = ['Could not find python source for imported module support code for %s. Looked for' % name] if idx == 2: msg.append('either %s.py or %s.py' % (py_module_name[-1], py_module_name[-2])) else: msg.append(py_module_name[-1]) raise AnsibleError(' '.join(msg)) if idx == 2: # We've determined that the last portion was an identifier and # thus, not part of the module name py_module_name = py_module_name[:-1] # If not already processed then we've got work to do # If not in the cache, then read the file into the cache # We already have a file handle for the module open so it makes # sense to read it now if py_module_name not in py_module_cache: if module_info[2][2] == imp.PKG_DIRECTORY: # Read the __init__.py instead of the module file as this is # a python package normalized_name = py_module_name + ('__init__',) if normalized_name not in py_module_names: normalized_path = os.path.join(os.path.join(module_info[1], '__init__.py')) normalized_data = _slurp(normalized_path) py_module_cache[normalized_name] = (normalized_data, normalized_path) normalized_modules.add(normalized_name) else: normalized_name = py_module_name if normalized_name not in py_module_names: normalized_path = module_info[1] normalized_data = module_info[0].read() module_info[0].close() py_module_cache[normalized_name] = (normalized_data, normalized_path) normalized_modules.add(normalized_name) # Make sure that all the packages that this module is a part of # are also added for i in range(1, len(py_module_name)): py_pkg_name = py_module_name[:-i] + ('__init__',) if py_pkg_name not in py_module_names: pkg_dir_info = imp.find_module(py_pkg_name[-1], [os.path.join(p, *py_pkg_name[:-1]) for p in module_utils_paths]) normalized_modules.add(py_pkg_name) py_module_cache[py_pkg_name] = (_slurp(pkg_dir_info[1]), pkg_dir_info[1]) # FIXME: Currently the AnsiBallZ wrapper monkeypatches module args into a global # variable in basic.py. If a module doesn't import basic.py, then the AnsiBallZ wrapper will # traceback when it tries to monkypatch. So, for now, we have to unconditionally include # basic.py. # # In the future we need to change the wrapper to monkeypatch the args into a global variable in # their own, separate python module. That way we won't require basic.py. Modules which don't # want basic.py can import that instead. AnsibleModule will need to change to import the vars # from the separate python module and mirror the args into its global variable for backwards # compatibility. if ('basic',) not in py_module_names: pkg_dir_info = imp.find_module('basic', module_utils_paths) normalized_modules.add(('basic',)) py_module_cache[('basic',)] = (_slurp(pkg_dir_info[1]), pkg_dir_info[1]) # End of AnsiballZ hack # # iterate through all of the ansible.module_utils* imports that we haven't # already checked for new imports # # set of modules that we haven't added to the zipfile unprocessed_py_module_names = normalized_modules.difference(py_module_names) for py_module_name in unprocessed_py_module_names: py_module_path = os.path.join(*py_module_name) py_module_file_name = '%s.py' % py_module_path zf.writestr(os.path.join("ansible/module_utils", py_module_file_name), py_module_cache[py_module_name][0]) display.vvvvv("Using module_utils file %s" % py_module_cache[py_module_name][1]) # Add the names of the files we're scheduling to examine in the loop to # py_module_names so that we don't re-examine them in the next pass # through recursive_finder() py_module_names.update(unprocessed_py_module_names) for py_module_file in unprocessed_py_module_names: recursive_finder(py_module_file, py_module_cache[py_module_file][0], py_module_names, py_module_cache, zf) # Save memory; the file won't have to be read again for this ansible module. del py_module_cache[py_module_file]
def fetch_file(self, in_path, out_path): super(Connection, self).fetch_file(in_path, out_path) in_path = self._shell._unquote(in_path) out_path = out_path.replace('\\', '/') display.vvv('FETCH "%s" TO "%s"' % (in_path, out_path), host=self._winrm_host) buffer_size = 2**19 # 0.5MB chunks makedirs_safe(os.path.dirname(out_path)) out_file = None try: offset = 0 while True: try: script = ''' $path = "%(path)s" If (Test-Path -Path $path -PathType Leaf) { $buffer_size = %(buffer_size)d $offset = %(offset)d $stream = New-Object -TypeName IO.FileStream($path, [IO.FileMode]::Open, [IO.FileAccess]::Read, [IO.FileShare]::ReadWrite) $stream.Seek($offset, [System.IO.SeekOrigin]::Begin) > $null $buffer = New-Object -TypeName byte[] $buffer_size $bytes_read = $stream.Read($buffer, 0, $buffer_size) if ($bytes_read -gt 0) { $bytes = $buffer[0..($bytes_read - 1)] [System.Convert]::ToBase64String($bytes) } $stream.Close() > $null } ElseIf (Test-Path -Path $path -PathType Container) { Write-Host "[DIR]"; } Else { Write-Error "$path does not exist"; Exit 1; } ''' % dict(buffer_size=buffer_size, path=self._shell._escape(in_path), offset=offset) display.vvvvv('WINRM FETCH "%s" to "%s" (offset=%d)' % (in_path, out_path, offset), host=self._winrm_host) cmd_parts = self._shell._encode_script(script, as_list=True, preserve_rc=False) result = self._winrm_exec(cmd_parts[0], cmd_parts[1:]) if result.status_code != 0: raise IOError(to_native(result.std_err)) if result.std_out.strip() == '[DIR]': data = None else: data = base64.b64decode(result.std_out.strip()) if data is None: makedirs_safe(out_path) break else: if not out_file: # If out_path is a directory and we're expecting a file, bail out now. if os.path.isdir(to_bytes(out_path, errors='surrogate_or_strict')): break out_file = open(to_bytes(out_path, errors='surrogate_or_strict'), 'wb') out_file.write(data) if len(data) < buffer_size: break offset += len(data) except Exception: traceback.print_exc() raise AnsibleError('failed to transfer file to "%s"' % to_native(out_path)) finally: if out_file: out_file.close()
def decrypt(self, vaulttext, filename=None): """Decrypt a piece of vault encrypted data. :arg vaulttext: a string to decrypt. Since vault encrypted data is an ascii text format this can be either a byte str or unicode string. :kwarg filename: a filename that the data came from. This is only used to make better error messages in case the data cannot be decrypted. :returns: a byte string containing the decrypted data """ b_vaulttext = to_bytes(vaulttext, errors='strict', encoding='utf-8') if self.secrets is None: raise AnsibleVaultError("A vault password must be specified to decrypt data") if not is_encrypted(b_vaulttext): msg = "input is not vault encrypted data" if filename: msg += "%s is not a vault encrypted file" % filename raise AnsibleError(msg) b_vaulttext, dummy, cipher_name, vault_id = parse_vaulttext_envelope(b_vaulttext) # create the cipher object, note that the cipher used for decrypt can # be different than the cipher used for encrypt if cipher_name in CIPHER_WHITELIST: this_cipher = CIPHER_MAPPING[cipher_name]() else: raise AnsibleError("{0} cipher could not be found".format(cipher_name)) b_plaintext = None if not self.secrets: raise AnsibleVaultError('Attempting to decrypt but no vault secrets found') # WARNING: Currently, the vault id is not required to match the vault id in the vault blob to # decrypt a vault properly. The vault id in the vault blob is not part of the encrypted # or signed vault payload. There is no cryptographic checking/verification/validation of the # vault blobs vault id. It can be tampered with and changed. The vault id is just a nick # name to use to pick the best secret and provide some ux/ui info. # iterate over all the applicable secrets (all of them by default) until one works... # if we specify a vault_id, only the corresponding vault secret is checked and # we check it first. vault_id_matchers = [] if vault_id: display.vvvvv('Found a vault_id (%s) in the vaulttext' % (vault_id)) vault_id_matchers.append(vault_id) _matches = match_secrets(self.secrets, vault_id_matchers) if _matches: display.vvvvv('We have a secret associated with vault id (%s), will try to use to decrypt %s' % (vault_id, filename)) else: display.vvvvv('Found a vault_id (%s) in the vault text, but we do not have a associated secret (--vault-id)' % (vault_id)) # Not adding the other secrets to vault_secret_ids enforces a match between the vault_id from the vault_text and # the known vault secrets. if not C.DEFAULT_VAULT_ID_MATCH: # Add all of the known vault_ids as candidates for decrypting a vault. vault_id_matchers.extend([_vault_id for _vault_id, _dummy in self.secrets if _vault_id != vault_id]) matched_secrets = match_secrets(self.secrets, vault_id_matchers) # for vault_secret_id in vault_secret_ids: for vault_secret_id, vault_secret in matched_secrets: display.vvvvv('Trying to use vault secret=(%s) id=%s to decrypt %s' % (vault_secret, vault_secret_id, filename)) try: # secret = self.secrets[vault_secret_id] display.vvvv('Trying secret %s for vault_id=%s' % (vault_secret, vault_secret_id)) b_plaintext = this_cipher.decrypt(b_vaulttext, vault_secret) if b_plaintext is not None: display.vvvvv('decrypt succesful with secret=%s and vault_id=%s' % (vault_secret, vault_secret_id)) break except AnsibleError as e: display.vvvv('Tried to use the vault secret (%s) to decrypt (%s) but it failed. Error: %s' % (vault_secret_id, filename, e)) continue else: msg = "Decryption failed (no vault secrets would found that could decrypt)" if filename: msg += " on %s" % filename raise AnsibleVaultError(msg) if b_plaintext is None: msg = "Decryption failed" if filename: msg += " on %s" % filename raise AnsibleError(msg) return b_plaintext
def path_dwim_relative_stack(self, paths, dirname, source, is_role=False): ''' find one file in first path in stack taking roles into account and adding play basedir as fallback :arg paths: A list of text strings which are the paths to look for the filename in. :arg dirname: A text string representing a directory. The directory is prepended to the source to form the path to search for. :arg source: A text string which is the filename to search for :rtype: A text string :returns: An absolute path to the filename ``source`` if found :raises: An AnsibleFileNotFound Exception if the file is found to exist in the search paths ''' b_dirname = to_bytes(dirname) b_source = to_bytes(source) result = None search = [] if source is None: display.warning( 'Invalid request to find a file that matches a "null" value') elif source and (source.startswith('~') or source.startswith(os.path.sep)): # path is absolute, no relative needed, check existence and return source test_path = unfrackpath(b_source, follow=False) if os.path.exists(to_bytes(test_path, errors='surrogate_or_strict')): result = test_path else: display.debug(u'evaluation_path:\n\t%s' % '\n\t'.join(paths)) for path in paths: upath = unfrackpath(path, follow=False) b_upath = to_bytes(upath, errors='surrogate_or_strict') b_mydir = os.path.dirname(b_upath) # if path is in role and 'tasks' not there already, add it into the search if (is_role or self._is_role(path)) and b_mydir.endswith(b'tasks'): search.append( os.path.join(os.path.dirname(b_mydir), b_dirname, b_source)) search.append(os.path.join(b_mydir, b_source)) else: # don't add dirname if user already is using it in source if b_source.split(b'/')[0] != dirname: search.append( os.path.join(b_upath, b_dirname, b_source)) search.append(os.path.join(b_upath, b_source)) # always append basedir as last resort # don't add dirname if user already is using it in source if b_source.split(b'/')[0] != dirname: search.append( os.path.join(to_bytes(self.get_basedir()), b_dirname, b_source)) search.append(os.path.join(to_bytes(self.get_basedir()), b_source)) display.debug(u'search_path:\n\t%s' % to_text(b'\n\t'.join(search))) for b_candidate in search: display.vvvvv(u'looking for "%s" at "%s"' % (source, to_text(b_candidate))) if os.path.exists(b_candidate): result = to_text(b_candidate) break if result is None: raise AnsibleFileNotFound(file_name=source, paths=[to_text(p) for p in search]) return result
def _winrm_exec(self, command, args=(), from_exec=False, stdin_iterator=None): if not self.protocol: self.protocol = self._winrm_connect() self._connected = True if from_exec: display.vvvvv("WINRM EXEC %r %r" % (command, args), host=self._winrm_host) else: display.vvvvvv("WINRM EXEC %r %r" % (command, args), host=self._winrm_host) command_id = None try: stdin_push_failed = False command_id = self.protocol.run_command( self.shell_id, to_bytes(command), map(to_bytes, args), console_mode_stdin=(stdin_iterator is None)) try: if stdin_iterator: for (data, is_last) in stdin_iterator: self._winrm_send_input(self.protocol, self.shell_id, command_id, data, eof=is_last) except Exception as ex: from traceback import format_exc display.warning("FATAL ERROR DURING FILE TRANSFER: %s" % to_text(ex)) stdin_push_failed = True # NB: this can hang if the receiver is still running (eg, network failed a Send request but the server's still happy). # FUTURE: Consider adding pywinrm status check/abort operations to see if the target is still running after a failure. resptuple = self.protocol.get_command_output( self.shell_id, command_id) # ensure stdout/stderr are text for py3 # FUTURE: this should probably be done internally by pywinrm response = Response( tuple( to_text(v) if isinstance(v, binary_type) else v for v in resptuple)) # TODO: check result from response and set stdin_push_failed if we have nonzero if from_exec: display.vvvvv('WINRM RESULT %r' % to_text(response), host=self._winrm_host) else: display.vvvvvv('WINRM RESULT %r' % to_text(response), host=self._winrm_host) display.vvvvvv('WINRM STDOUT %s' % to_text(response.std_out), host=self._winrm_host) display.vvvvvv('WINRM STDERR %s' % to_text(response.std_err), host=self._winrm_host) if stdin_push_failed: stderr = to_bytes(response.std_err, encoding='utf-8') if self.is_clixml(stderr): stderr = self.parse_clixml_stream(stderr) raise AnsibleError( 'winrm send_input failed; \nstdout: %s\nstderr %s' % (response.std_out, stderr)) return response finally: if command_id: self.protocol.cleanup_command(self.shell_id, command_id)
def _parse_pipeline_result(self, pipeline): """ PSRP doesn't have the same concept as other protocols with its output. We need some extra logic to convert the pipeline streams and host output into the format that Ansible understands. :param pipeline: The finished PowerShell pipeline that invoked our commands :return: rc, stdout, stderr based on the pipeline output """ # we try and get the rc from our host implementation, this is set if # exit or $host.SetShouldExit() is called in our pipeline, if not we # set to 0 if the pipeline had not errors and 1 if it did rc = self.host.rc or (1 if pipeline.had_errors else 0) # TODO: figure out a better way of merging this with the host output stdout_list = [] for output in pipeline.output: # not all pipeline outputs can be casted to a string, we will # create our own output based on the properties if that is the # case+ try: output_msg = str(output) except TypeError: if isinstance(output, GenericComplexObject): obj_lines = output.property_sets for key, value in output.adapted_properties.items(): obj_lines.append("%s: %s" % (key, value)) for key, value in output.extended_properties.items(): obj_lines.append("%s: %s" % (key, value)) output_msg = "\n".join(obj_lines) else: output_msg = "" stdout_list.append(output_msg) stdout = "\r\n".join(stdout_list) if len(self.host.ui.stdout) > 0: stdout += "\r\n" + "".join(self.host.ui.stdout) stderr_list = [] for error in pipeline.streams.error: # the error record is not as fully fleshed out like we usually get # in PS, we will manually create it here error_msg = "%s : %s\r\n" \ "%s\r\n" \ " + CategoryInfo : %s\r\n" \ " + FullyQualifiedErrorId : %s" \ % (error.command_name, str(error), error.invocation_position_message, error.message, error.fq_error) stacktrace = error.script_stacktrace if self._play_context.verbosity >= 3 and stacktrace is not None: error_msg += "\r\nStackTrace:\r\n%s" % stacktrace stderr_list.append(error_msg) stderr = "\r\n".join(stderr_list) if len(self.host.ui.stderr) > 0: stderr += "\r\n" + "".join(self.host.ui.stderr) display.vvvvv("PSRP RC: %d" % rc, host=self._psrp_host) display.vvvvv("PSRP STDOUT: %s" % stdout, host=self._psrp_host) display.vvvvv("PSRP STDERR: %s" % stderr, host=self._psrp_host) # reset the host back output back to defaults, needed if running # multiple pipelines on the same RunspacePool self.host.rc = 0 self.host.ui.stdout = [] self.host.ui.stderr = [] return rc, stdout, stderr
def close(self): """ unmount container's filesystem """ super(Connection, self).close() rc, stdout, stderr = self._buildah("umount") display.vvvvv("RC %s STDOUT %r STDERR %r" % (rc, stdout, stderr)) self._connected = False
def fetch_file(self, in_path, out_path): super(Connection, self).fetch_file(in_path, out_path) display.vvv("FETCH %s TO %s" % (in_path, out_path), host=self._psrp_host) in_path = self._shell._unquote(in_path) out_path = out_path.replace('\\', '/') # because we are dealing with base64 data we need to get the max size # of the bytes that the base64 size would equal max_b64_size = int(self.runspace.connection.max_payload_size - (self.runspace.connection.max_payload_size / 4 * 3)) buffer_size = max_b64_size - (max_b64_size % 1024) # setup the file stream with read only mode setup_script = '''$ErrorActionPreference = "Stop" $path = "%s" if (Test-Path -Path $path -PathType Leaf) { $fs = New-Object -TypeName System.IO.FileStream -ArgumentList @( $path, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::Read ) $buffer_size = %d } elseif (Test-Path -Path $path -PathType Container) { Write-Output -InputObject "[DIR]" } else { Write-Error -Message "$path does not exist" $host.SetShouldExit(1) }''' % (self._shell._escape(in_path), buffer_size) # read the file stream at the offset and return the b64 string read_script = '''$ErrorActionPreference = "Stop" $fs.Seek(%d, [System.IO.SeekOrigin]::Begin) > $null $buffer = New-Object -TypeName byte[] -ArgumentList $buffer_size $bytes_read = $fs.Read($buffer, 0, $buffer_size) if ($bytes_read -gt 0) { $bytes = $buffer[0..($bytes_read - 1)] Write-Output -InputObject ([System.Convert]::ToBase64String($bytes)) }''' # need to run the setup script outside of the local scope so the # file stream stays active between fetch operations rc, stdout, stderr = self._exec_psrp_script(setup_script, use_local_scope=False) if rc != 0: raise AnsibleError( "failed to setup file stream for fetch '%s': %s" % (out_path, to_native(stderr))) elif stdout.strip() == '[DIR]': # in_path was a dir so we need to create the dir locally makedirs_safe(out_path) return b_out_path = to_bytes(out_path, errors='surrogate_or_strict') makedirs_safe(os.path.dirname(b_out_path)) offset = 0 with open(b_out_path, 'wb') as out_file: while True: display.vvvvv("PSRP FETCH %s to %s (offset=%d" % (in_path, out_path, offset), host=self._psrp_host) rc, stdout, stderr = \ self._exec_psrp_script(read_script % offset) if rc != 0: raise AnsibleError("failed to transfer file to '%s': %s" % (out_path, to_native(stderr))) data = base64.b64decode(stdout.strip()) out_file.write(data) if len(data) < buffer_size: break rc, stdout, stderr = self._exec_psrp_script("$fs.Close()") if rc != 0: display.warning("failed to close remote file stream of file " "'%s': %s" % (in_path, to_native(stderr)))
def put_file(self, in_path, out_path): super(Connection, self).put_file(in_path, out_path) display.vvv("PUT %s TO %s" % (in_path, out_path), host=self._psrp_host) out_path = self._shell._unquote(out_path) script = u'''begin { $ErrorActionPreference = "Stop" $path = '%s' $fd = [System.IO.File]::Create($path) $algo = [System.Security.Cryptography.SHA1CryptoServiceProvider]::Create() $bytes = @() } process { $bytes = [System.Convert]::FromBase64String($input) $algo.TransformBlock($bytes, 0, $bytes.Length, $bytes, 0) > $null $fd.Write($bytes, 0, $bytes.Length) } end { $fd.Close() $algo.TransformFinalBlock($bytes, 0, 0) > $null $hash = [System.BitConverter]::ToString($algo.Hash) $hash = $hash.Replace("-", "").ToLowerInvariant() Write-Output -InputObject "{`"sha1`":`"$hash`"}" }''' % self._shell._escape(out_path) cmd_parts = self._shell._encode_script(script, as_list=True, strict_mode=False, preserve_rc=False) b_in_path = to_bytes(in_path, errors='surrogate_or_strict') if not os.path.exists(b_in_path): raise AnsibleFileNotFound('file or module does not exist: "%s"' % to_native(in_path)) in_size = os.path.getsize(b_in_path) buffer_size = int(self.runspace.connection.max_payload_size / 4 * 3) # copying files is faster when using the raw WinRM shell and not PSRP # we will create a WinRS shell just for this process # TODO: speed this up as there is overhead creating a shell for this with WinRS(self.runspace.connection, codepage=65001) as shell: process = Process(shell, cmd_parts[0], cmd_parts[1:]) process.begin_invoke() offset = 0 with open(b_in_path, 'rb') as src_file: for data in iter((lambda: src_file.read(buffer_size)), b""): offset += len(data) display.vvvvv("PSRP PUT %s to %s (offset=%d, size=%d" % (in_path, out_path, offset, len(data)), host=self._psrp_host) b64_data = base64.b64encode(data) + b"\r\n" process.send(b64_data, end=(src_file.tell() == in_size)) # the file was empty, return empty buffer if offset == 0: process.send(b"", end=True) process.end_invoke() process.signal(SignalCode.CTRL_C) if process.rc != 0: raise AnsibleError(to_native(process.stderr)) put_output = json.loads(process.stdout) remote_sha1 = put_output.get("sha1") if not remote_sha1: raise AnsibleError( "Remote sha1 was not returned, stdout: '%s', " "stderr: '%s'" % (to_native(process.stdout), to_native(process.stderr))) local_sha1 = secure_hash(in_path) if not remote_sha1 == local_sha1: raise AnsibleError("Remote sha1 hash %s does not match local hash " "%s" % (to_native(remote_sha1), to_native(local_sha1)))
def fetch_file(self, in_path, out_path): super(Connection, self).fetch_file(in_path, out_path) in_path = self._shell._unquote(in_path) out_path = out_path.replace('\\', '/') display.vvv('FETCH "%s" TO "%s"' % (in_path, out_path), host=self._winrm_host) buffer_size = 2**19 # 0.5MB chunks makedirs_safe(os.path.dirname(out_path)) out_file = None try: offset = 0 while True: try: script = ''' If (Test-Path -PathType Leaf "%(path)s") { $stream = [System.IO.File]::OpenRead("%(path)s"); $stream.Seek(%(offset)d, [System.IO.SeekOrigin]::Begin) | Out-Null; $buffer = New-Object Byte[] %(buffer_size)d; $bytesRead = $stream.Read($buffer, 0, %(buffer_size)d); $bytes = $buffer[0..($bytesRead-1)]; [System.Convert]::ToBase64String($bytes); $stream.Close() | Out-Null; } ElseIf (Test-Path -PathType Container "%(path)s") { Write-Host "[DIR]"; } Else { Write-Error "%(path)s does not exist"; Exit 1; } ''' % dict(buffer_size=buffer_size, path=self._shell._escape(in_path), offset=offset) display.vvvvv('WINRM FETCH "%s" to "%s" (offset=%d)' % (in_path, out_path, offset), host=self._winrm_host) cmd_parts = self._shell._encode_script(script, as_list=True) result = self._winrm_exec(cmd_parts[0], cmd_parts[1:]) if result.status_code != 0: raise IOError(to_str(result.std_err)) if result.std_out.strip() == '[DIR]': data = None else: data = base64.b64decode(result.std_out.strip()) if data is None: makedirs_safe(out_path) break else: if not out_file: # If out_path is a directory and we're expecting a file, bail out now. if os.path.isdir(out_path): break out_file = open(out_path, 'wb') out_file.write(data) if len(data) < buffer_size: break offset += len(data) except Exception: traceback.print_exc() raise AnsibleError('failed to transfer file to "%s"' % out_path) finally: if out_file: out_file.close()
def _winrm_exec(self, command, args=(), from_exec=False, stdin_iterator=None): if not self.protocol: self.protocol = self._winrm_connect() self._connected = True if from_exec: display.vvvvv("WINRM EXEC %r %r" % (command, args), host=self._winrm_host) else: display.vvvvvv("WINRM EXEC %r %r" % (command, args), host=self._winrm_host) command_id = None try: stdin_push_failed = False command_id = self.protocol.run_command( self.shell_id, to_bytes(command), map(to_bytes, args), console_mode_stdin=(stdin_iterator is None)) # TODO: try/except around this, so we can get/return the command result on a broken pipe or other failure (probably more useful than the 500 that # comes from this) try: if stdin_iterator: for (data, is_last) in stdin_iterator: self._winrm_send_input(self.protocol, self.shell_id, command_id, data, eof=is_last) except Exception as ex: from traceback import format_exc display.warning("FATAL ERROR DURING FILE TRANSFER: %s" % format_exc(ex)) stdin_push_failed = True if stdin_push_failed: raise AnsibleError('winrm send_input failed') # NB: this can hang if the receiver is still running (eg, network failed a Send request but the server's still happy). # FUTURE: Consider adding pywinrm status check/abort operations to see if the target is still running after a failure. response = Response( self.protocol.get_command_output(self.shell_id, command_id)) # TODO: check result from response and set stdin_push_failed if we have nonzero if from_exec: display.vvvvv('WINRM RESULT %r' % to_text(response), host=self._winrm_host) else: display.vvvvvv('WINRM RESULT %r' % to_text(response), host=self._winrm_host) display.vvvvvv('WINRM STDOUT %s' % to_text(response.std_out), host=self._winrm_host) display.vvvvvv('WINRM STDERR %s' % to_text(response.std_err), host=self._winrm_host) if stdin_push_failed: raise AnsibleError( 'winrm send_input failed; \nstdout: %s\nstderr %s' % (response.std_out, response.std_err)) return response finally: if command_id: self.protocol.cleanup_command(self.shell_id, command_id)
def recursive_finder(name, data, py_module_names, py_module_cache, zf): """ Using ModuleDepFinder, make sure we have all of the module_utils files that the module its module_utils files needs. """ # Parse the module and find the imports of ansible.module_utils tree = ast.parse(data) finder = ModuleDepFinder() finder.visit(tree) # # Determine what imports that we've found are modules (vs class, function. # variable names) for packages # normalized_modules = set() # Loop through the imports that we've found to normalize them # Exclude paths that match with paths we've already processed # (Have to exclude them a second time once the paths are processed) module_utils_paths = [p for p in module_utils_loader._get_paths(subdirs=False) if os.path.isdir(p)] module_utils_paths.append(_MODULE_UTILS_PATH) for py_module_name in finder.submodules.difference(py_module_names): module_info = None if py_module_name[0] == 'six': # Special case the python six library because it messes up the # import process in an incompatible way module_info = imp.find_module('six', module_utils_paths) py_module_name = ('six',) idx = 0 elif py_module_name[0] == '_six': # Special case the python six library because it messes up the # import process in an incompatible way module_info = imp.find_module('_six', [os.path.join(p, 'six') for p in module_utils_paths]) py_module_name = ('six', '_six') idx = 0 else: # Check whether either the last or the second to last identifier is # a module name for idx in (1, 2): if len(py_module_name) < idx: break try: module_info = imp.find_module(py_module_name[-idx], [os.path.join(p, *py_module_name[:-idx]) for p in module_utils_paths]) break except ImportError: continue # Could not find the module. Construct a helpful error message. if module_info is None: msg = ['Could not find imported module support code for %s. Looked for' % (name,)] if idx == 2: msg.append('either %s.py or %s.py' % (py_module_name[-1], py_module_name[-2])) else: msg.append(py_module_name[-1]) raise AnsibleError(' '.join(msg)) # Found a byte compiled file rather than source. We cannot send byte # compiled over the wire as the python version might be different. # imp.find_module seems to prefer to return source packages so we just # error out if imp.find_module returns byte compiled files (This is # fragile as it depends on undocumented imp.find_module behaviour) if module_info[2][2] not in (imp.PY_SOURCE, imp.PKG_DIRECTORY): msg = ['Could not find python source for imported module support code for %s. Looked for' % name] if idx == 2: msg.append('either %s.py or %s.py' % (py_module_name[-1], py_module_name[-2])) else: msg.append(py_module_name[-1]) raise AnsibleError(' '.join(msg)) if idx == 2: # We've determined that the last portion was an identifier and # thus, not part of the module name py_module_name = py_module_name[:-1] # If not already processed then we've got work to do if py_module_name not in py_module_names: # If not in the cache, then read the file into the cache # We already have a file handle for the module open so it makes # sense to read it now if py_module_name not in py_module_cache: if module_info[2][2] == imp.PKG_DIRECTORY: # Read the __init__.py instead of the module file as this is # a python package normalized_name = py_module_name + ('__init__',) normalized_path = os.path.join(os.path.join(module_info[1], '__init__.py')) normalized_data = _slurp(normalized_path) else: normalized_name = py_module_name normalized_path = module_info[1] normalized_data = module_info[0].read() module_info[0].close() py_module_cache[normalized_name] = (normalized_data, normalized_path) normalized_modules.add(normalized_name) # Make sure that all the packages that this module is a part of # are also added for i in range(1, len(py_module_name)): py_pkg_name = py_module_name[:-i] + ('__init__',) if py_pkg_name not in py_module_names: pkg_dir_info = imp.find_module(py_pkg_name[-1], [os.path.join(p, *py_pkg_name[:-1]) for p in module_utils_paths]) normalized_modules.add(py_pkg_name) py_module_cache[py_pkg_name] = (_slurp(pkg_dir_info[1]), pkg_dir_info[1]) # # iterate through all of the ansible.module_utils* imports that we haven't # already checked for new imports # # set of modules that we haven't added to the zipfile unprocessed_py_module_names = normalized_modules.difference(py_module_names) for py_module_name in unprocessed_py_module_names: py_module_path = os.path.join(*py_module_name) py_module_file_name = '%s.py' % py_module_path zf.writestr(os.path.join("ansible/module_utils", py_module_file_name), py_module_cache[py_module_name][0]) display.vvvvv("Using module_utils file %s" % py_module_cache[py_module_name][1]) # Add the names of the files we're scheduling to examine in the loop to # py_module_names so that we don't re-examine them in the next pass # through recursive_finder() py_module_names.update(unprocessed_py_module_names) for py_module_file in unprocessed_py_module_names: recursive_finder(py_module_file, py_module_cache[py_module_file][0], py_module_names, py_module_cache, zf) # Save memory; the file won't have to be read again for this ansible module. del py_module_cache[py_module_file]
def fetch_file(self, in_path, out_path): super(Connection, self).fetch_file(in_path, out_path) in_path = self._shell._unquote(in_path) out_path = out_path.replace('\\', '/') display.vvv('FETCH "%s" TO "%s"' % (in_path, out_path), host=self._winrm_host) buffer_size = 2**19 # 0.5MB chunks makedirs_safe(os.path.dirname(out_path)) out_file = None try: offset = 0 while True: try: script = ''' If (Test-Path -PathType Leaf "%(path)s") { $stream = New-Object IO.FileStream("%(path)s", [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, [IO.FileShare]::ReadWrite); $stream.Seek(%(offset)d, [System.IO.SeekOrigin]::Begin) | Out-Null; $buffer = New-Object Byte[] %(buffer_size)d; $bytesRead = $stream.Read($buffer, 0, %(buffer_size)d); $bytes = $buffer[0..($bytesRead-1)]; [System.Convert]::ToBase64String($bytes); $stream.Close() | Out-Null; } ElseIf (Test-Path -PathType Container "%(path)s") { Write-Host "[DIR]"; } Else { Write-Error "%(path)s does not exist"; Exit 1; } ''' % dict(buffer_size=buffer_size, path=self._shell._escape(in_path), offset=offset) display.vvvvv('WINRM FETCH "%s" to "%s" (offset=%d)' % (in_path, out_path, offset), host=self._winrm_host) cmd_parts = self._shell._encode_script(script, as_list=True, preserve_rc=False) result = self._winrm_exec(cmd_parts[0], cmd_parts[1:]) if result.status_code != 0: raise IOError(to_native(result.std_err)) if result.std_out.strip() == '[DIR]': data = None else: data = base64.b64decode(result.std_out.strip()) if data is None: makedirs_safe(out_path) break else: if not out_file: # If out_path is a directory and we're expecting a file, bail out now. if os.path.isdir( to_bytes(out_path, errors='surrogate_or_strict')): break out_file = open( to_bytes(out_path, errors='surrogate_or_strict'), 'wb') out_file.write(data) if len(data) < buffer_size: break offset += len(data) except Exception: traceback.print_exc() raise AnsibleError('failed to transfer file to "%s"' % out_path) finally: if out_file: out_file.close()
def setup_vault_secrets(loader, vault_ids, vault_password_files=None, ask_vault_pass=None, create_new_password=False, auto_prompt=True): # list of tuples vault_secrets = [] # Depending on the vault_id value (including how --ask-vault-pass / --vault-password-file create a vault_id) # we need to show different prompts. This is for compat with older Towers that expect a # certain vault password prompt format, so 'promp_ask_vault_pass' vault_id gets the old format. prompt_formats = {} # If there are configured default vault identities, they are considered 'first' # so we prepend them to vault_ids (from cli) here vault_password_files = vault_password_files or [] if C.DEFAULT_VAULT_PASSWORD_FILE: vault_password_files.append(C.DEFAULT_VAULT_PASSWORD_FILE) if create_new_password: prompt_formats['prompt'] = [ 'New vault password (%(vault_id)s): ', 'Confirm vew vault password (%(vault_id)s): ' ] # 2.3 format prompts for --ask-vault-pass prompt_formats['prompt_ask_vault_pass'] = [ 'New Vault password: '******'Confirm New Vault password: '******'prompt'] = ['Vault password (%(vault_id)s): '] # The format when we use just --ask-vault-pass needs to match 'Vault password:\s*?$' prompt_formats['prompt_ask_vault_pass'] = ['Vault password: '******'prompt', 'prompt_ask_vault_pass']: # prompts cant/shouldnt work without a tty, so dont add prompt secrets if not sys.stdin.isatty(): continue # --vault-id some_name@prompt_ask_vault_pass --vault-id other_name@prompt_ask_vault_pass will be a little # confusing since it will use the old format without the vault id in the prompt built_vault_id = vault_id_name or C.DEFAULT_VAULT_IDENTITY # choose the prompt based on --vault-id=prompt or --ask-vault-pass. --ask-vault-pass # always gets the old format for Tower compatibility. # ie, we used --ask-vault-pass, so we need to use the old vault password prompt # format since Tower needs to match on that format. prompted_vault_secret = PromptVaultSecret( prompt_formats=prompt_formats[vault_id_value], vault_id=built_vault_id) # a empty or invalid password from the prompt will warn and continue to the next # without erroring globablly try: prompted_vault_secret.load() except AnsibleError as exc: display.warning('Error in vault password prompt (%s): %s' % (vault_id_name, exc)) raise vault_secrets.append((built_vault_id, prompted_vault_secret)) # update loader with new secrets incrementally, so we can load a vault password # that is encrypted with a vault secret provided earlier loader.set_vault_secrets(vault_secrets) continue # assuming anything else is a password file display.vvvvv('Reading vault password file: %s' % vault_id_value) # read vault_pass from a file file_vault_secret = get_file_vault_secret(filename=vault_id_value, vault_id=vault_id_name, loader=loader) # an invalid password file will error globally try: file_vault_secret.load() except AnsibleError as exc: display.warning( 'Error in vault password file loading (%s): %s' % (vault_id_name, exc)) raise if vault_id_name: vault_secrets.append((vault_id_name, file_vault_secret)) else: vault_secrets.append( (C.DEFAULT_VAULT_IDENTITY, file_vault_secret)) # update loader with as-yet-known vault secrets loader.set_vault_secrets(vault_secrets) return vault_secrets
def setup_vault_secrets(loader, vault_ids, vault_password_files=None, ask_vault_pass=None, create_new_password=False): # list of tuples vault_secrets = [] # Depending on the vault_id value (including how --ask-vault-pass / --vault-password-file create a vault_id) # we need to show different prompts. This is for compat with older Towers that expect a # certain vault password prompt format, so 'promp_ask_vault_pass' vault_id gets the old format. prompt_formats = {} vault_password_files = vault_password_files or [] if C.DEFAULT_VAULT_PASSWORD_FILE: vault_password_files.append(C.DEFAULT_VAULT_PASSWORD_FILE) if create_new_password: prompt_formats['prompt'] = [ 'New vault password (%(vault_id)s): ', 'Confirm vew vault password (%(vault_id)s): ' ] else: prompt_formats['prompt'] = ['Vault password (%(vault_id)s): '] # The format when we use just --ask-vault-pass needs to match 'Vault password:\s*?$' prompt_formats['prompt_ask_vault_pass'] = ['Vault password: '******'prompt', 'prompt_ask_vault_pass']: # --vault-id some_name@prompt_ask_vault_pass --vault-id other_name@prompt_ask_vault_pass will be a little # confusing since it will use the old format without the vault id in the prompt if vault_id_name: prompted_vault_secret = PromptVaultSecret( prompt_formats=prompt_formats[vault_id_value], vault_id=vault_id_name) prompted_vault_secret.load() vault_secrets.append( (vault_id_name, prompted_vault_secret)) else: # ie, we used --ask-vault-pass, so we need to use the old vault password prompt # format since Tower needs to match on that format. prompted_vault_secret = PromptVaultSecret( prompt_formats=prompt_formats[vault_id_value], vault_id=C.DEFAULT_VAULT_IDENTITY) prompted_vault_secret.load() vault_secrets.append( (C.DEFAULT_VAULT_IDENTITY, prompted_vault_secret)) # update loader with new secrets incrementally, so we can load a vault password # that is encrypted with a vault secret provided earlier loader.set_vault_secrets(vault_secrets) continue # assuming anything else is a password file display.vvvvv('Reading vault password file: %s' % vault_id_value) # read vault_pass from a file file_vault_secret = get_file_vault_secret( filename=vault_id_value, vault_id_name=vault_id_name, loader=loader) file_vault_secret.load() if vault_id_name: vault_secrets.append((vault_id_name, file_vault_secret)) else: vault_secrets.append( (C.DEFAULT_VAULT_IDENTITY, file_vault_secret)) # update loader with as-yet-known vault secrets loader.set_vault_secrets(vault_secrets) return vault_secrets