利用Python Fabric配置主机间SSH互信和添加公钥

简介:

本文主要讲述如何利用Python的Fabric模块编写一个脚本用于配置多个主机间SSH互信以及如何将管理员自己的公钥批量添加到多个主机中。

脚本说明

该脚本只提供如题所述的少量功能,用于帮助熟悉Python的Fabric和SSH几项简单的基本配置,原本的目的是想通过Python和Fabric实现对主机进行一些批量操作,如完成主机的初始化等。因为SSH的配置具有通用性和必要性,所以便有了此文,希望对Linux运维和使用Python、Fabric自动化部署感兴趣的人有所帮助。

该脚本将继续维护,直至使用Python和Fabric完成主机的初始化这个脚本在生产环境可用,可以关注GitHub上LinuxBashShellScriptForOps项目的更新信息

说明:LinuxBashShellScriptForOps是一个Linux运维脚本仓库,对在Linux运维工作所能用到的Shell脚本和Python脚本的归纳和总结。 绝大部分的源码均出自生产系统并经过比较严谨的测试,可以通过阅读该项目的README获得该项目的更多信息。

涉及的主要知识

其中涉及的知识:

  1. Python编程知识

  2. Fabric模块配置和使用相关知识

    1. 定义主机角色、用户名和密码、sudo用户名和密码、SSH配置和连接配置等

    2. 使用设置上下文 (with setting),用于一些需要Fabric特殊设置的执行过程,如在执行命令出错时是否该弹出警告并不退出当前任务

    3. sudo执行命令

    4. 获取远程命令的返回结果(标准输出和标准错误输出,但不包括返回结果值0,1)

    5. 终端多颜色显示

    6. 特殊操作需要用户确认(confirm函数)

    7. runs_once装饰器,注意runs_once装饰器不能与定义的角色中有多个主机的情况下联用

  3. 日志模块的配置已经隐含在脚本中,非常有用但没有使用,原因在本文第二段已经说明

  4. 使用Python判断当前系统是Windows还是Linux,是Debian系还是RHEL系

  5. 使用Python判断IP是否合法

功能说明

此脚本以Ubuntu为例,主要有三个功能:

  1. 重置主机自动生成的SSH Key

  2. 将管理员自己的SSH 公钥注入到root用户的SSH认证文件,从而可以通过key和以root身份登录到主机

  3. 将其他(同一网段或者能访问到)主机的SSH 公钥添加到root用户的SSH认证文件

注:是否要重置主机自动生成的SSH Key取决于当前环境下主机是如何安装操作系统的,如果是通过模板的方式批量部署的虚拟机通常拥有相同的SSH配置,这就意味着它们的SSH配置完全相同,包括公钥、私钥、SSH配置和用户SSH配置等。如果这是公有云环境,可能导致安全问题,就像通过模板安装Windows需要sysprep安装删除唯一性信息一样,Linux中SSH也必须重置。这些key通常位于/etc/ssh目录下:

/etc/ssh/ssh_host_dsa_key     
/etc/ssh/ssh_host_dsa_key.pub      
/etc/ssh/ssh_host_ecdsa_key      
/etc/ssh/ssh_host_ecdsa_key.pub      
/etc/ssh/ssh_host_ed25519_key      
/etc/ssh/ssh_host_ed25519_key.pub      
/etc/ssh/ssh_host_rsa_key      
/etc/ssh/ssh_host_rsa_key.pub

脚本内容

脚本内容如下,也可以从GitHub获取:

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
#!/usr/bin/python
# encoding: utf-8
# -*- coding: utf8 -*-
"""
Created by PyCharm.
File:               LinuxBashShellScriptForOps:pyLinuxHostsSSHKeyInitialization.py
User:               Guodong
Create Date:        2017/3/9
Create Time:        23:05
  """
import  time
import  os
import  logging
import  logging.handlers
import  sys
from  fabric.api  import  *
from  fabric.colors  import  red, green, yellow, blue, magenta, cyan, white
from  fabric.context_managers  import  *
from  fabric.contrib.console  import  confirm
 
env.roledefs  =  {
     'base' : [ 'ubuntu@192.168.1.101:22' , ],
     "bigData" : [ 'ubuntu@192.168.100.122:22' 'ubuntu@192.168.100.123:22' 'ubuntu@192.168.100.124:22' , ],
     "coreServices" : [ 'ubuntu@192.168.100.127:22' 'ubuntu@192.168.100.128:22' 'ubuntu@192.168.100.129:22' ,
                      'ubuntu@192.168.100.130:22' , ],
     "webAppFrontend" : [ 'ubuntu@192.168.100.125:22' , ],
     "webAppBackend" : [ 'ubuntu@192.168.100.126:22' , ],
     'all' : [ '192.168.1.101' '192.168.100.122' '192.168.100.123' '192.168.100.124' '192.168.100.125' ,
             '192.168.100.126' '192.168.100.127' '192.168.100.128' '192.168.100.129' '192.168.100.130' , ],
     'db' : [ 'ubuntu@192.168.100.127:22' , ],
     'nginx' : [ 'ubuntu@192.168.100.128:22' , ],
}
 
env.hosts  =  [ '192.168.1.101' '192.168.100.122' '192.168.100.123' '192.168.100.124' '192.168.100.125' ,
              '192.168.100.126' '192.168.100.127' '192.168.100.128' '192.168.100.129' '192.168.100.130' , ]
env.port  =  '22'
env.user  =  "ubuntu"
env.password  =  "ubuntu"
env.sudo_user  =  "root"   # fixed setting, it must be 'root'
env.sudo_password  =  "ubuntu"
env.disable_known_hosts  =  True
env.warn_only  =  False
env.command_timeout  =  15
env.connection_attempts  =  2
 
 
def  initLoggerWithRotate(logPath = "/var/log" , logName = None , singleLogFile = True ):
     current_time  =  time.strftime( "%Y%m%d%H" )
     if  logName  is  not  None  and  not  singleLogFile:
         logPath  =  os.path.join(logPath, logName)
         logFilename  =  logName  +  "_"  +  current_time  +  ".log"
     elif  logName  is  not  None  and  singleLogFile:
         logPath  =  os.path.join(logPath, logName)
         logFilename  =  logName  +  ".log"
     else :
         logName  =  "default"
         logFilename  =  logName  +  ".log"
 
     if  not  os.path.exists(logPath):
         os.makedirs(logPath)
         logFilename  =  os.path.join(logPath, logFilename)
     else :
         logFilename  =  os.path.join(logPath, logFilename)
 
     logger  =  logging.getLogger(logName)
     log_formatter  =  logging.Formatter( "%(asctime)s %(filename)s:%(lineno)d %(name)s %(levelname)s: %(message)s" ,
                                       "%Y-%m-%d %H:%M:%S" )
     file_handler  =  logging.handlers.RotatingFileHandler(logFilename, maxBytes = 104857600 , backupCount = 5 )
     file_handler.setFormatter(log_formatter)
     stream_handler  =  logging.StreamHandler(sys.stderr)
     logger.addHandler(file_handler)
     logger.addHandler(stream_handler)
     logger.setLevel(logging.DEBUG)
     return  logger
 
 
mswindows  =  (sys.platform  = =  "win32" )   # learning from 'subprocess' module
linux  =  (sys.platform  = =  "linux2" )
 
 
def  _win_or_linux():
     # os.name ->(sames to) sys.builtin_module_names
     if  'posix'  in  sys.builtin_module_names:
         os_type  =  'Linux'
     elif  'nt'  in  sys.builtin_module_names:
         os_type  =  'Windows'
     return  os_type
 
 
def  is_windows():
     if  "windows"  in  _win_or_linux().lower():
         return  True
     else :
         return  False
 
 
def  is_linux():
     if  "linux"  in  _win_or_linux().lower():
         return  True
     else :
         return  False
 
 
def  is_debian_family():
     import  platform
     # http://stackoverflow.com/questions/2988017/string-comparison-in-python-is-vs
     # http://stackoverflow.com/questions/1504717/why-does-comparing-strings-in-python-using-either-or-is-sometimes-produce
     if  platform.system()  = =  "Linux" :
         distname  =  platform.linux_distribution()
         if  "Ubuntu"  in  distname  or  "Debian"  in  distname:
             return  True
         else :
             return  False
     else :
         return  False
 
 
def  is_rhel_family():
     import  platform
     if  platform.system()  = =  "Linux" :
         distname  =  platform.linux_distribution()
         if  "CentOS"  in  distname  or  "Debian"  in  distname:
             return  True
         else :
             return  False
     else :
         return  False
 
 
# log_path = "/var/log" if os.path.exists("/var/log") or os.makedirs("/var/log") else "/var/log"
log_path  =  "/var/log"
log_name  =  "."  +  os.path.splitext(os.path.basename(__file__))[ 0 ]
 
log  =  initLoggerWithRotate(logPath = "/var/log" , logName = log_name, singleLogFile = True )
log.setLevel(logging.INFO)
 
 
def  is_valid_ipv4(ip, version = 4 ):
     from  IPy  import  IP
     try :
         result  =  IP(ip, ipversion = version)
     except  ValueError:
         return  False
     if  result  is  not  None  and  result ! =  "":
         return  True
 
 
@roles ( 'all' )
def  reset_ssh_public_host_key():
     """
     First job to run
     Reset ssh public host key after clone from one same virtual machine template
     Repeat do this will disable ssh connect between different hosts which ssh key has been registered!
     :return:
     """
     with settings(warn_only = False ):
         out  =  sudo( "test -f /etc/ssh/unique.lck && cat /etc/ssh/unique.lck" , combine_stderr = False , warn_only = True )
         print  yellow(
             "Repeat do this will disable ssh connect between different hosts which ssh key has been registered!" )
 
         if  "1"  not  in  out:
             if  confirm( "Are you really want to reset ssh public key on this host? " ):
                 blue( "Reconfigure openssh-server with dpkg" )
                 sudo( "rm /etc/ssh/ssh_host_* && dpkg-reconfigure openssh-server && echo 1 >/etc/ssh/unique.lck" )
             else :
                 print  green( "Brilliant, user canceled this dangerous operation." )
         else :
             print  blue( "If you see a 'Warning' in red color here, do not panic, this is normal when first time to run." )
             print  green( "ssh public host key is ok." )
 
 
@roles ( 'all' )
def  inject_admin_ssh_public_key():
     """
     Second job to run
     Inject Admin user's ssh key to each host
     :return:
     """
     with settings(warn_only = False ):
         sudo( 'yes | ssh-keygen -N "" -f /root/.ssh/id_rsa' )
         content_ssh_public_key  =  """ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCawuOgQup3Qc1OILytyH+u3S9te85ctEKTvzPtRjHfnEEOjpRS6v6/PsuDHplHO1PAm8cKbEZmqR9tg4mWSweosBYW7blUUB4yWfBu6cHAnJOZ7ADNWHHJHAYi8QFZd4SLAAKbf9J12Xrkw2qZkdUyTBVbm+Y8Ay9bHqGX7KKLhjt0FIqQHRizcvncBFHXbCTJWsAduj2i7GQ5vJ507+MgFl2ZTKD2BGX5m0Jq9z3NTJD7fEb2J6RxC9PypYjayXyQBhgACxaBrPXRdYVXmy3f3zRQ4/OmJvkgoSodB7fYL8tcUZWSoXFa33vdPlVlBYx91uuA6onvOXDnryo3frN1
ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAumQ2srRwd9slaeYTdr/dGd0H4NzJ3uQdBQABTe/nhJsUFWVG3titj7JiOYjCb54dmpHoi4rAYIElwrolQttZSCDKTVjamnzXfbV8HvJapLLLJTdKraSXhiUkdS4D004uleMpaqhmgNxCLu7onesCCWQzsNw9Hgpx5Hicpko6Xh0=
"""
         sudo( "echo -e '%s' >/root/.ssh/authorized_keys"  %  content_ssh_public_key)
 
 
@roles ( 'all' )
def  scan_host_ssh_public_key():
     """
     Third and last job to run
     scan all host's public key, then inject to /root/.ssh/authorized_keys
     :return:
     """
     with settings(warn_only = False ):
         for  host  in  env.hosts:
             if  is_valid_ipv4(host):
                 sudo(
                     r """ssh-keyscan -t rsa %s |& awk -F '[ ]+' '!/^#/ {print $2" "$3}' >>/root/.ssh/authorized_keys"""
                     %  host)
 
 
@roles ( 'all' )
def  config_ssh_connection():
     reset_ssh_public_host_key()
     inject_admin_ssh_public_key()
     scan_host_ssh_public_key()
 
 
def  terminal_debug_win32(func):
     command  =  "fab  - i c:\Users\Guodong\.ssh\exportedkey201310171355\
                 - % s \
                 % s"  %  (__file__, func)
     os.system(command)
 
 
def  terminal_debug_posix(func):
     command  =  "fab  - / etc / ssh / ssh_host_rsa_key\
                 - % s \
                 % s"  %  (__file__, func)
     os.system(command)
 
 
if  __name__  = =  '__main__' :
     import  re
 
     if  len (sys.argv)  = =  1 :
         if  is_windows():
             terminal_debug_win32( "config_ssh_connection" )
             sys.exit( 0 )
         if  is_linux():
             terminal_debug_posix( "config_ssh_connection" )
             sys.exit( 0 )
 
     sys.argv[ 0 =  re.sub(r '(-script\.pyw|\.exe)?$' , '', sys.argv[ 0 ])
     print  red( "Please use 'fab -f %s'"  %  " " .join( str (x)  for  in  sys.argv[ 0 :]))
     sys.exit( 1 )

参考信息

一些有用的链接和参考信息:

  1. Fabric中文文档

  2. Fabric是什么

  3. 关于云服务器相同系统镜像模板中OpenSSH密钥相同的处理方法

tag:SSH互信,SSH公钥,Fabric

--end--



本文转自 urey_pp 51CTO博客,原文链接:http://blog.51cto.com/dgd2010/1905039,如需转载请自行联系原作者




相关文章
|
2天前
|
Python
新手向 Python:VsCode环境下Manim配置
该文介绍了如何准备和配置开发环境以使用Manim,主要包括两个步骤:一是准备工作,需要下载并安装VsCode和Anaconda,其中Anaconda需添加到系统PATH环境变量,并通过清华镜像源配置;二是配置环境,VsCode中安装中文插件和Python扩展,激活并配置虚拟环境。最后,安装ffmpeg和manim,通过VsCode运行测试代码验证配置成功。
16 1
|
3天前
|
安全 Shell 网络安全
GitHub SSH 快速配置
这是一个自动化脚本,用于简化设置 Git SSH 连接的过程。功能包括:设置 Git 用户名和邮箱、生成新的 SSH 密钥、将 SSH 私钥添加到 ssh-agent、可选复制 SSH 公钥到剪贴板、提供 GitHub 配置指引以及测试 SSH 连接。用户需确保安装 Git 和 SSH 工具,然后下载脚本并赋予执行权限,按照提示操作即可。适合 Git 初学者或需要快速配置 SSH 的用户。
|
3天前
|
缓存 Linux 编译器
Linux(CentOS7.5) 安装部署 Python3.6(超详细!包含 Yum 源配置!)
该指南介绍了在Linux系统中配置Yum源和安装Python3的步骤。首先,通过`yum install`和`wget`命令更新和备份Yum源,并从阿里云获取CentOS和EPEL的repo文件。接着,清理和更新Yum缓存。然后,下载Python3源代码包,推荐使用阿里云镜像加速。解压后,安装必要的依赖,如gcc。在配置和编译Python3时,可能需要解决缺少C编译器的问题。完成安装后,创建Python3和pip3的软链接,并更新环境变量。最后,验证Python3安装成功,并可选地升级pip和配置pip源以提高包下载速度。
|
9天前
|
网络协议 程序员 网络架构
最全OSPF路由协议基础(OSPF基本配置),2024年最新Python高级面试
最全OSPF路由协议基础(OSPF基本配置),2024年最新Python高级面试
最全OSPF路由协议基础(OSPF基本配置),2024年最新Python高级面试
|
9天前
|
IDE 开发工具 开发者
2024年最新5个提升生产效率的Python开发和配置的小技巧_python高级开发技巧,字节跳动面试必问
2024年最新5个提升生产效率的Python开发和配置的小技巧_python高级开发技巧,字节跳动面试必问
2024年最新5个提升生产效率的Python开发和配置的小技巧_python高级开发技巧,字节跳动面试必问
|
11天前
|
C++ Python
vs配置python环境 - 蓝易云
以上就是在Visual Studio中配置Python环境的步骤,希望对你有所帮助。
17 1
|
12天前
|
机器学习/深度学习 Java 数据挖掘
selenium的配置与基本使用(1),2024年最新网易Python面试必问
selenium的配置与基本使用(1),2024年最新网易Python面试必问
|
12天前
|
前端开发 Unix Linux
Sublime Text 3配置 Python 开发环境
【5月更文挑战第7天】本篇 Huazie 介绍了 Sublime Text 3 配置 Python 开发环境的相关内容,感兴趣的朋友赶紧配置起来,有任何问题可以随时评论区沟通。
35 1
Sublime Text 3配置 Python 开发环境
|
14天前
|
网络安全
|
14天前
|
弹性计算 运维 Shell
基于key验证多主机ssh访问
【4月更文挑战第30天】
24 1