Ansible默认是通过SSH key和远程被控制主机进行通信,当然我们可以SSH password来和远程主机进行通信。 如果使用SSH KEY,则要将控制主机上的公钥放到被监控主机的/root/.ssh/authorized_keys文件中,这种方式也是官方提倡的。毕竟直接使用密码存在一定的风险。

密钥认证原理

一、通过ssh密钥免密登录

创建密钥对 ssh-keygen(more)

1
2
# 解决创建秘钥对需要进行交互的问题
ssh-keygen -t dsa -f ~/.ssh/id_dsa -P "" -q

分发公钥文件

ssh-copy-id 手动分发公钥文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[root@sltkp3cbpch ~]# ssh-copy-id -i /root/.ssh/id_dsa.pub -p 22 root@10.122.60.68
/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/root/.ssh/id_dsa.pub"
The authenticity of host '10.122.60.68 (10.122.60.68)' can't be established.
ECDSA key fingerprint is SHA256:RHy25keGgWs+/k9ETi13P8KLjvaFS0P0kV1W9DLQgW8.
ECDSA key fingerprint is MD5:25:c5:04:8e:5f:16:21:f1:53:9d:b2:bb:00:36:0e:08.
Are you sure you want to continue connecting (yes/no)? yes # 可通过ansible.cfg配置"host_key_checking = False"跳过 ssh 首次连接提示验证部分
/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
root@10.122.60.68's password: # 远程被管理端密码

Number of key(s) added: 1

Now try logging into the machine, with: "ssh -p '22' 'root@10.122.60.68'"
and check to make sure that only the key(s) you wanted were added.

[root@sltkp3cbpch ~]#

基于ssh key的登录验证:

1
2
[root@sltkp3cbpch ~]# ssh root@10.122.60.68 "hostname -i"
fe80::f816:3eff:fe4b:25a1%eth0 10.122.60.68 172.17.0.1 172.18.0.1

sshpass 批量分发公钥

问题

  1. 创建秘钥对需要进行交互
    • 需要确认秘钥保存路径
    • 需要确认密码信息
  2. 分发公钥时需要进行交互
    • 首次ssh连接时需要进行确认yes|no
    • 第一次分发公钥需要进行密码认证

解决 创建秘钥 时需要进行的交互

1
ssh-keygen -t dsa -f ~/.ssh/id_dsa -P "" -q

参数说明:

-f:filename Specifies the filename of the key file.
    指定密钥文件保存的路径信息(免交互)
-N:提供一个新密语;
-P:提供(旧)密语;
-q:安静的 不输出信息,减少信息输出

解决 第一次分发公钥 时需要进行的交互(sshpass)

1
2
sshpass -p $clientpasswd ssh-copy-id -p22 -i /root/.ssh/id_dsa.pub root@$ip -o StrictHostKeyChecking=no

参数说明:

-o option 选择 (man 手册中可以查到有很多选项)
StrictHostKeyChecking=no 对询问的回应(不进行对密钥检查)

要实现免密码,需要一款软件 sshpass 该软件就是为ssh提供密码使用的

1
yum install sshpass -y

分发脚本

非交互式创建/分发密钥:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#!/bin/bash
# check key pair
ID_DSA='/root/.ssh/id_dsa'
ID_PUB='/root/.ssh/id_dsa.pub'

# distribute public key
function Distribute_pubkey (){
for ip in 83 68
do
#sshpass -p $clientpasswd ssh-copy-id -p22 -i /root/.ssh/id_dsa.pub root@$ip -o StrictHostKeyChecking=no
sshpass -p"$GKLPASSWD" ssh-copy-id -p22 -i /root/.ssh/id_dsa.pub root@10.122.60."$ip" -o StrictHostKeyChecking=no >/dev/null 2>&1
if [ $? -ne 0 ];then
echo -e "distribute public key to ip-$ip is false"
else
echo -e "distribute public key to ip-$ip is true"
fi
done
}

if [ -f "$ID_DSA" ] && [ -f "$ID_PUB" ];then
echo "key pair is already exists, Start distributing pub files"
Distribute_pubkey
else
# make key pair
ssh-keygen -t dsa -f ~/.ssh/id_dsa -P "" -q
Distribute_pubkey
fi

ansible免密登录验证

添加ansible hosts,编辑 /etc/ansible/hosts

1
2
3
[nginx-servers]
10.122.60.68
10.122.60.83

验证:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[root@sltkp3cbpch ansible]# ansible all -m "ping"
[DEPRECATION WARNING]: The TRANSFORM_INVALID_GROUP_CHARS settings is set to allow bad characters in group names by default,
this will change, but still be user configurable on deprecation. This feature will be removed in version 2.10. Deprecation
warnings can be disabled by setting deprecation_warnings=False in ansible.cfg.
[WARNING]: Invalid characters were found in group names but not replaced, use -vvvv to see details
10.122.60.68 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false,
"ping": "pong"
}
10.122.60.83 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false,
"ping": "pong"
}

二、ansible 账号密码登录(more)

上一章中,我们利用sshpass命令完成了批量推送公钥的操作,使ansible可以通过SSH key进行免密登录来管理远程主机。

本章将学习ansible通过SSH password来和远程主机进行通信。
需要用到的hosts文件内部支持的俩个特定指令(inventory参数):

  • ansible_ssh_user:连接到该主机的ssh用户
  • ansible_ssh_pass:连接到该主机的ssh密码(连-k选项都省了),安全考虑还是建议使用私钥或在命令行指定-k选项输入

准备工作:
首先,为了不影响实验效果,请将推送完成的公钥信息从远程被控制主机authorized_keys中删除并验证环境:

1
2
3
4
5
6
7
8
9
10
[root@sltkp3cbpch ansible]# ansible all -m "ping"
...
...
10.122.60.83 | UNREACHABLE! => {
"changed": false,
"msg": "Failed to connect to the host via ssh: Permission denied (publickey,gssapi-keyex,gssapi-with-mic,password).",
"unreachable": true
}
...
...

添加ansible hosts

$vim /etc/ansible/hosts

格式:【主机名】 【主机地址】 【主机密码】 默认是root用户来进行的

1
2
3
[nginx-servers]
1 ansible_ssh_user="root" ansible_ssh_host=10.122.60.68 ansible_ssh_pass="houjue1992***"
2 ansible_ssh_user="root" ansible_ssh_host=10.122.60.83 ansible_ssh_pass="houjue1992***"

新版的ansible(2.4+) hosts有更新, 用以下方式:

1
2
3
[nginx-servers]
10.122.60.68 ansible_user=root ansible_ssh_pass="houjue1992***"
10.122.60.83 ansible_user=root ansible_ssh_pass="houjue1992***"

ansible账号密码登录验证

1
2
3
4
5
6
7
8
9
10
11
12
[root@sltkp3cbpch ansible]# ansible all -m "ping"
...
...
10.122.60.68 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false,
"ping": "pong"
}
...
...

小结

参考文章:https://blog.csdn.net/liumiaocn/article/details/82354158

ansible在执行的时候实际还是使用了ssh,可以不必打通ssh通道,但是实际在执行的时候需要输入用户名和密码,而这两个信息,可以通过/etc/ansible/hosts中的选项进行设定:ansible_ssh_useransible_ssh_pass
另外,没有打通ssh通道的时候,ansible使用了sshpass , 而sshpass则正是免去交互式输入用户名密码的ssh命令。

ansible 在执行时使用sshpass的过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
Parsed /etc/ansible/hosts inventory source with ini plugin
META: ran handlers
<10.122.60.68> ESTABLISH SSH CONNECTION FOR USER: root
<10.122.60.68> SSH: EXEC sshpass -d10 ssh -C -o ControlMaster=auto -o ControlPersist=60s -o 'User="root"' -o ConnectTimeout=10 -o ControlPath=/root/.ansible/cp/2611c79bbc 10.122.60.68 '/bin/sh -c '"'"'echo ~root && sleep 0'"'"''
<10.122.60.68> (0, '/root\n', '')
<10.122.60.68> ESTABLISH SSH CONNECTION FOR USER: root
<10.122.60.68> SSH: EXEC sshpass -d10 ssh -C -o ControlMaster=auto -o ControlPersist=60s -o 'User="root"' -o ConnectTimeout=10 -o ControlPath=/root/.ansible/cp/2611c79bbc 10.122.60.68 '/bin/sh -c '"'"'( umask 77 && mkdir -p "` echo /root/.ansible/tmp `"&& mkdir /root/.ansible/tmp/ansible-tmp-1588928466.74-17075-281433475095247 && echo ansible-tmp-1588928466.74-17075-281433475095247="` echo /root/.ansible/tmp/ansible-tmp-1588928466.74-17075-281433475095247 `" ) && sleep 0'"'"''
<10.122.60.68> (0, 'ansible-tmp-1588928466.74-17075-281433475095247=/root/.ansible/tmp/ansible-tmp-1588928466.74-17075-281433475095247\n', '')
<10.122.60.68> Attempting python interpreter discovery
<10.122.60.68> ESTABLISH SSH CONNECTION FOR USER: root
<10.122.60.68> SSH: EXEC sshpass -d10 ssh -C -o ControlMaster=auto -o ControlPersist=60s -o 'User="root"' -o ConnectTimeout=10 -o ControlPath=/root/.ansible/cp/2611c79bbc 10.122.60.68 '/bin/sh -c '"'"'echo PLATFORM; uname; echo FOUND; command -v '"'"'"'"'"'"'"'"'/usr/bin/python'"'"'"'"'"'"'"'"'; command -v '"'"'"'"'"'"'"'"'python3.7'"'"'"'"'"'"'"'"'; command -v '"'"'"'"'"'"'"'"'python3.6'"'"'"'"'"'"'"'"'; command -v '"'"'"'"'"'"'"'"'python3.5'"'"'"'"'"'"'"'"'; command -v '"'"'"'"'"'"'"'"'python2.7'"'"'"'"'"'"'"'"'; command -v '"'"'"'"'"'"'"'"'python2.6'"'"'"'"'"'"'"'"'; command -v '"'"'"'"'"'"'"'"'/usr/libexec/platform-python'"'"'"'"'"'"'"'"'; command -v '"'"'"'"'"'"'"'"'/usr/bin/python3'"'"'"'"'"'"'"'"'; command -v '"'"'"'"'"'"'"'"'python'"'"'"'"'"'"'"'"'; echo ENDFOUND && sleep 0'"'"''
<10.122.60.68> (0, 'PLATFORM\nLinux\nFOUND\n/usr/bin/python\n/usr/bin/python3.6\n/usr/bin/python2.7\n/usr/libexec/platform-python\n/usr/bin/python3\n/usr/bin/python\nENDFOUND\n', '')
<10.122.60.68> ESTABLISH SSH CONNECTION FOR USER: root
<10.122.60.68> SSH: EXEC sshpass -d10 ssh -C -o ControlMaster=auto -o ControlPersist=60s -o 'User="root"' -o ConnectTimeout=10 -o ControlPath=/root/.ansible/cp/2611c79bbc 10.122.60.68 '/bin/sh -c '"'"'/usr/bin/python && sleep 0'"'"''
<10.122.60.68> (0, '{"osrelease_content": "NAME=\\"CentOS Linux\\"\\nVERSION=\\"7 (Core)\\"\\nID=\\"centos\\"\\nID_LIKE=\\"rhel fedora\\"\\nVERSION_ID=\\"7\\"\\nPRETTY_NAME=\\"CentOS Linux 7 (Core)\\"\\nANSI_COLOR=\\"0;31\\"\\nCPE_NAME=\\"cpe:/o:centos:centos:7\\"\\nHOME_URL=\\"https://www.centos.org/\\"\\nBUG_REPORT_URL=\\"https://bugs.centos.org/\\"\\n\\nCENTOS_MANTISBT_PROJECT=\\"CentOS-7\\"\\nCENTOS_MANTISBT_PROJECT_VERSION=\\"7\\"\\nREDHAT_SUPPORT_PRODUCT=\\"centos\\"\\nREDHAT_SUPPORT_PRODUCT_VERSION=\\"7\\"\\n\\n", "platform_dist_result": ["centos", "7.5.1804", "Core"]}\n', '')
Using module file /usr/lib/python2.7/site-packages/ansible/modules/system/ping.py
<10.122.60.68> PUT /root/.ansible/tmp/ansible-local-17067x6om3y/tmp1Exf0H TO /root/.ansible/tmp/ansible-tmp-1588928466.74-17075-281433475095247/AnsiballZ_ping.py
<10.122.60.68> SSH: EXEC sshpass -d10 sftp -o BatchMode=no -b - -C -o ControlMaster=auto -o ControlPersist=60s -o 'User="root"' -o ConnectTimeout=10 -o ControlPath=/root/.ansible/cp/2611c79bbc '[10.122.60.68]'
<10.122.60.68> (0, 'sftp> put /root/.ansible/tmp/ansible-local-17067x6om3y/tmp1Exf0H /root/.ansible/tmp/ansible-tmp-1588928466.74-17075-281433475095247/AnsiballZ_ping.py\n', '')
<10.122.60.68> ESTABLISH SSH CONNECTION FOR USER: root
<10.122.60.68> SSH: EXEC sshpass -d10 ssh -C -o ControlMaster=auto -o ControlPersist=60s -o 'User="root"' -o ConnectTimeout=10 -o ControlPath=/root/.ansible/cp/2611c79bbc 10.122.60.68 '/bin/sh -c '"'"'chmod u+x /root/.ansible/tmp/ansible-tmp-1588928466.74-17075-281433475095247/ /root/.ansible/tmp/ansible-tmp-1588928466.74-17075-281433475095247/AnsiballZ_ping.py && sleep 0'"'"''
<10.122.60.68> (0, '', '')
<10.122.60.68> ESTABLISH SSH CONNECTION FOR USER: root
<10.122.60.68> SSH: EXEC sshpass -d10 ssh -C -o ControlMaster=auto -o ControlPersist=60s -o 'User="root"' -o ConnectTimeout=10 -o ControlPath=/root/.ansible/cp/2611c79bbc -tt 10.122.60.68 '/bin/sh -c '"'"'/usr/bin/python /root/.ansible/tmp/ansible-tmp-1588928466.74-17075-281433475095247/AnsiballZ_ping.py && sleep 0'"'"''
<10.122.60.68> (0, '\r\n{"invocation": {"module_args": {"data": "pong"}}, "ping": "pong"}\r\n', 'Shared connection to 10.122.60.68 closed.\r\n')
<10.122.60.68> ESTABLISH SSH CONNECTION FOR USER: root
<10.122.60.68> SSH: EXEC sshpass -d10 ssh -C -o ControlMaster=auto -o ControlPersist=60s -o 'User="root"' -o ConnectTimeout=10 -o ControlPath=/root/.ansible/cp/2611c79bbc 10.122.60.68 '/bin/sh -c '"'"'rm -f -r /root/.ansible/tmp/ansible-tmp-1588928466.74-17075-281433475095247/ > /dev/null 2>&1 && sleep 0'"'"''
<10.122.60.68> (0, '', '')
10.122.60.68 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false,
"invocation": {
"module_args": {
"data": "pong"
}
},
"ping": "pong"
}
META: ran handlers
META: ran handlers

三、使用authorized_key模块推送公钥(more)

结合上面学习的内容,我们对比ansible管理远程主机的两种手段:

  1. SSH key:将控制主机上的公钥放到被监控主机的/root/.ssh/authorized_keys文件中,这种方式安全并且是官方提倡的。
  2. SSH password:ansible使用ssh通过读取hosts中配置的账号密码来登录并控制远程主机,这种方法存在账号密码泄漏的风险。

由此可见,SSH key 安全且一本万利,但是前提是需要进行公钥推送的操作,而通过sshpass命令完成这个任务就需要进行脚本编写,无疑增加了工作量。此时,authorized_key模块就显得格外方便。


两个重要命令:

  • ssh-keygen :这个命令是用来生成本机的公钥和私钥的
  • ssh-keyscan : 这条命令是用来把远程服务器的公钥来获取到本地的

首先关闭公钥认证

如果说不想关闭公钥认证的话,可以用ssh-keycan 命令将公钥添加到本地的known_hosts文件里面去
具体命令是ssh-keyscan IP1 (IP2 ...) >> /root/.ssh/known_hosts 可以添加多个

Ansible1.2.1及其之后的版本都会默认启用公钥认证.

公钥认证就是如果之后的某一台客户端和之前登录过的某一台主机IP相同,那么在“known_hosts”中有了不同的key,这时会提示一个错误信息直到被纠正为止。
在使用Ansible时,可能不想遇到那样的情况,如果有个主机没有在“known_hosts”中被初始化将会导致在交互使用Ansible或定时执行Ansible时对key信息的确认提示。如果想要禁用这个行为的话,可以关闭公钥认证,而且公钥认证会比较慢,也是提高效率的一个方法

1
2
# 认证提示:
"msg": "Using a SSH password instead of a key is not possible because Host Key checking is enabled and sshpass does not support this. Please add this host's fingerprint to your known_hosts file to manage this host."

关闭公钥认证,编辑ansible.cfg配置文件:

1
2
# 用来禁止ssh的指纹key字串检查
host_key_checking = False

或者直接设置环境变量:export ANSIBLE_HOST_KEY_CHECKING=False

使用ssh-key产生公钥和私钥

1
2
# 可参考第一章节内容
ssh-keygen -t dsa -f ~/.ssh/id_dsa -P ''

添加ansible hosts

$vim /etc/ansible/hosts

格式:【主机名】 【主机地址】 【主机密码】 默认是root用户来进行的

1
2
3
[nginx-servers]
1 ansible_ssh_user="root" ansible_ssh_host=10.122.60.68 ansible_ssh_pass="houjue1992***"
2 ansible_ssh_user="root" ansible_ssh_host=10.122.60.83 ansible_ssh_pass="houjue1992***"

新版的ansible(2.4+) hosts有更新, 用以下方式:

1
2
3
[nginx-servers]
10.122.60.68 ansible_user=root ansible_ssh_pass="houjue1992***"
10.122.60.83 ansible_user=root ansible_ssh_pass="houjue1992***"

批量推送公钥到远程机器

方法1:编写Playbook剧本文件

1
2
3
4
5
6
7
8
9
10
11
12
13
# put_ssh_pub_key.yml  是基于YAML语言编写的

- hosts: all
#gather_facts: no
remote_user: root

tasks:
- name: install ssh key
authorized_key:
user: root
key: "{{ lookup('file', '/root/.ssh/id_dsa.pub') }}"
state: present

1
ansible-playbook -i hosts put_ssh_pub_key.yml

方法2:ansible命令行调用authorized_key模块

1
ansible nginx-server -m authorized_key -a "user=root state=present key='{{ lookup('file', '/root/.ssh/id_dsa.pub') }}'"

通过上面的方法,我们将ansible管理端的公钥批量推送后,就建立了ssh通道、实现了免密登录,这时hosts文件中配置的ansible_useransible_ssh_pass参数就没有用途了,我们需要进行安全优化操作,类似如下效果:

1
2
3
[nginx-servers]
10.122.60.68
10.122.60.83

如果,ansible管理所有远程主机使用的账号密码是统一的一个,那么hosts文件中可以不进行账号信息的配置并保障了信息安全,而是执行ansible命令指定authorized_key模块时,通过-k选项,输入远程主机的统一密码,即可实现批量推送ssh公钥。

1
ansible nginx-server -m authorized_key -a "user=root state=present key='{{ lookup('file', '/root/.ssh/id_dsa.pub') }}'" -k

5. ansible-playbook 免密登录优化

占位符