Ansible源码解析:在动态清单中使用Host Variable存储ssh登录凭证

(本文基于Ansible 2.7)

前两天有位朋友问到是否可以使用用户名+口令的登录方式来代替预先分发RSA公钥到目标主机以实现免密登录的方式,这无疑是可以的,而且很简单。只需要在Inventory中给host增加两个variable,它们分别是:

  • ansible_ssh_user
  • ansible_ssh_pass
    并安装sshpass包,以便使用非交互的方式提供登录口令。

在lib/ansible/constants.py中定义了一个字典——MAGIC_VARIABLE_MAPPING :

MAGIC_VARIABLE_MAPPING = dict(

    # base
    connection=('ansible_connection', ),
    module_compression=('ansible_module_compression', ),
    shell=('ansible_shell_type', ),
    executable=('ansible_shell_executable', ),

    # connection common
    remote_addr=('ansible_ssh_host', 'ansible_host'),
    remote_user=('ansible_ssh_user', 'ansible_user'),
    password=('ansible_ssh_pass', 'ansible_password'),
    port=('ansible_ssh_port', 'ansible_port'),
    pipelining=('ansible_ssh_pipelining', 'ansible_pipelining'),
    timeout=('ansible_ssh_timeout', 'ansible_timeout'),
    private_key_file=('ansible_ssh_private_key_file', 'ansible_private_key_file'),

    # networking modules
    network_os=('ansible_network_os', ),
    connection_user=('ansible_connection_user',),

    # ssh TODO: remove
    ssh_executable=('ansible_ssh_executable', ),
    ssh_common_args=('ansible_ssh_common_args', ),
    sftp_extra_args=('ansible_sftp_extra_args', ),
    scp_extra_args=('ansible_scp_extra_args', ),
    ssh_extra_args=('ansible_ssh_extra_args', ),
    ssh_transfer_method=('ansible_ssh_transfer_method', ),

    # docker TODO: remove
    docker_extra_args=('ansible_docker_extra_args', ),

    # become
    become=('ansible_become', ),
    become_method=('ansible_become_method', ),
    become_user=('ansible_become_user', ),
    become_pass=('ansible_become_password', 'ansible_become_pass'),
    become_exe=('ansible_become_exe', ),
    become_flags=('ansible_become_flags', ),

    # deprecated
    sudo=('ansible_sudo', ),
    sudo_user=('ansible_sudo_user', ),
    sudo_pass=('ansible_sudo_password', 'ansible_sudo_pass'),
    sudo_exe=('ansible_sudo_exe', ),
    sudo_flags=('ansible_sudo_flags', ),
    su=('ansible_su', ),
    su_user=('ansible_su_user', ),
    su_pass=('ansible_su_password', 'ansible_su_pass'),
    su_exe=('ansible_su_exe', ),
    su_flags=('ansible_su_flags', ),
)

这个字典使用来做变量名映射的(这句是废话,看命名就看出来了)
简单总结一下,就是如果发现有变量存在于该字典的values中,则将该变量的值赋给变量名为values对应的key的变量。如:变量中如果存在ansible_ssh_port或ansible_port,那么它的值会被赋给port。

下面是ansible源码的分析过程:
MAGIC_VARIABLE_MAPPING 这个字典主要在lib/ansible/playbook/play_context.py中的set_task_and_variable_override方法有多处使用,其中涉及本文的,在366-379行:

        attrs_considered = []
        for (attr, variable_names) in iteritems(C.MAGIC_VARIABLE_MAPPING):
            for variable_name in variable_names:
                if attr in attrs_considered:
                    continue
                # if delegation task ONLY use delegated host vars, avoid delegated FOR host vars
                if task.delegate_to is not None:
                    if isinstance(delegated_vars, dict) and variable_name in delegated_vars:
                        setattr(new_info, attr, delegated_vars[variable_name])
                        attrs_considered.append(attr)
                elif variable_name in variables:
                    setattr(new_info, attr, variables[variable_name])
                    attrs_considered.append(attr)
                # no else, as no other vars should be considered

set_task_and_variable_override这个方法在StrategyBase._execute_meta和TaskExecutor._execute中调用,variables是传入的参数。其中,TaskExecutor._execute由TaskExecutor.run调用,而TaskExecutor.run又是WorkerProcess.run中实际执行task并返回结果的核心内容(详见lib/ansible/executor/process/worker.py),在WorkerProcess中的变量是包含了host variable的。正是在WorkerProcess.run去具体执行一个任务的时候,把inventory中的host变量ansible_ssh_user和ansible_ssh_pass的值覆盖到了PlayContext的remote_user,password。

remote_user和password这两个变量的使用,在两个connection plugin中:

lib/ansible/plugins/ssh.py中的_build_command方法,549-559行,做了sshpass环境的相关检查,就是看有没有安装这个包:

        if self._play_context.password:
            if not self._sshpass_available():
                raise AnsibleError("to use the 'ssh' connection type with passwords, you must install the sshpass program")

614-620行,拼接了登录的用户名:

        user = self._play_context.remote_user
        if user:
            self._add_args(
                b_command,
                (b"-o", b"User=" + to_bytes(self._play_context.remote_user, errors='surrogate_or_strict')),
                u"ANSIBLE_REMOTE_USER/remote_user/ansible_user/user/-u set"
            )

_bare_run方法的793-804行,写入口令:

        # If we are using SSH password authentication, write the password into
        # the pipe we opened in _build_command.

        if self._play_context.password:
            os.close(self.sshpass_pipe[0])
            try:
                os.write(self.sshpass_pipe[1], to_bytes(self._play_context.password) + b'\n')
            except OSError as e:
                # Ignore broken pipe errors if the sshpass process has exited.
                if e.errno != errno.EPIPE or p.poll() is None:
                    raise
            os.close(self.sshpass_pipe[1])

lib/ansible/plugins/paramiko_ssh.py的_connect_uncached方法,353-363行,用用户名和口令执行ssh连接:

            ssh.connect(
                self._play_context.remote_addr.lower(),
                username=self._play_context.remote_user,
                allow_agent=allow_agent,
                look_for_keys=self.get_option('look_for_keys'),
                key_filename=key_filename,
                password=self._play_context.password,
                timeout=self._play_context.timeout,
                port=port,
                **ssh_connect_kwargs
            )

以及479-486行的_connect_sftp方法(许多传输文件的操作,例如fetch模块,如果使用paramiko_ssh连接目标,就是使用sftp来实现的):

    def _connect_sftp(self):

        cache_key = "%s__%s__" % (self._play_context.remote_addr, self._play_context.remote_user)
        if cache_key in SFTP_CONNECTION_CACHE:
            return SFTP_CONNECTION_CACHE[cache_key]
        else:
            result = SFTP_CONNECTION_CACHE[cache_key] = self._connect().ssh.open_sftp()
            return result

OK,至此在动态清单中使用Host Variable存储ssh登录凭证并使用改凭证连接目标主机的过程就说完了。其实最有参考意义的是MAGIC_VARIABLE_MAPPING 这个字典。除ansible_ssh_user和ansible_ssh_pass之外,还有其他的诸多变量,可以放置在host variable中,并起到相应的作用。

你可能感兴趣的:(Ansible,ansible)