利用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获取:

#!/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‘: [‘[email protected]:22‘, ],
    "bigData": [‘[email protected]:22‘, ‘[email protected]:22‘, ‘[email protected]:22‘, ],
    "coreServices": [‘[email protected]:22‘, ‘ub[email protected]:22‘, ‘[email protected]:22‘,
                     ‘[email protected]:22‘, ],
    "webAppFrontend": [‘[email protected]:22‘, ],
    "webAppBackend": [‘[email protected]: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‘: [‘[email protected]:22‘, ],
    ‘nginx‘: [‘[email protected]: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                -f %s                 %s" % (__file__, func)
    os.system(command)

def terminal_debug_posix(func):
    command = "fab -i /etc/ssh/ssh_host_rsa_key                -f %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 x in sys.argv[0:]))
    sys.exit(1)

参考信息

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

  1. Fabric中文文档
  2. Fabric是什么
  3. 关于云服务器相同系统镜像模板中OpenSSH密钥相同的处理方法

tag:SSH互信,SSH公钥,Fabric

--end--

时间: 2024-07-28 17:04:24

利用Python Fabric配置主机间SSH互信和添加公钥的相关文章

Solaris ssh配置主机间信任关系

假设需要配置从主机com00biiitf001登录主机ols00biiitf001时不需要密码,则采用以下步骤配置: com00biiitf001上产生公用/私有密钥对 $ ssh-keygen -t rsa Generating public/private rsa key pair. Enter file in which to save the key (/export/home/jyu/.ssh/id_rsa): Created directory '/export/home/jyu/.

Linux主机间ssh实现无密码登陆

server1    主机名:centos6         IP:192.168.2.105        操作系统:centos6.5 server2    主机名:rhel6             IP:192.168.2.110        操作系统:rhel6.5 为了实现server1对server2能够实现无密码登陆,可以在server1主机上使用ssh-keygen工具生成一对密钥,server1保留私钥,将公钥上传至server2主机相应用户的主目录下的.ssh/文件夹下,

linux主机基于ssh互信配置

大概就是说:我使用ssh-keygen在当前家目录创建了一个公钥,通ssh-copy-id -i 把公钥发送过.输入指定对方的用户名密码,然后我就可以登录到你了.互相无密码ssh登录的操作步骤一样! 1. 2.如果不是默认的22端口,加-p选项 3.

centos主机建立ssh互信

ssh-keygen 生成密钥 1.ssh-keygen -t rsa 可以加密和签名 rsa 只能加密不能签名 2.ssh-copy-id -i /root/.ssh/id_rsa.pub [email protected]  公钥 -i指定密钥文件 单向的登录 命令执行ssh  [email protected] “COMMAND” 返回到本地主机

Docker:使用Ambassador进行跨主机间容器通信

由于Docker自身的网络的原因,想要在多主机间的容器之间进行通信是比较麻烦的事情.可以利用Ambassador容器来实现这一功能. 基本原理: 利用Ambassador来实现主机间容器进行通信时,需要在两台需要通信的容器的主机上都启动Ambassador容器.由Ambassador容器提供数据转发服务. 当客户端主机上的容器client_container想要同服务器端主机上的容器server_container通信时,client_container容器直接访问同一台主机上运行的client

linux服务器配置域名ssh 互信

主机信息 192.168.10.10  node1.zzx.com 192.168.10.11  node2.zzx.com 分别配置主机域名 hostname node1.zzx.com hostname node2.zzx.com 分别修改配置 vim /etc/sysconfig/network 修改 HOSTNAME=node1.zzx.com HOSTNAEM=node2.zzx.com vim /etc/hosts 添加 192.168.10.10 node1.zzx.com nod

Python Fabric ssh 配置解读

Python Fabric ssh 配置解读 Fabric 2.4简介: Fabric is a high level Python (2.7, 3.4+) library designed to execute shell commands remotely over SSH, yielding useful Python objects in return. 简单说就是一个基于 ssh 执行远程 shell 命令返回一个 python 对象的一个 python 库. Fabric 的大部分配

[Python Fabric] [SSH] Mac OS X 10.9 + Vagrant虚拟环境使用Python Fabric进行SSH远程登录的简单实验

1. ssh客户端生成key 1 $ ssh-keygen -t rsa -b 4096 2 Generating public/private rsa key pair. 3 Enter file in which to save the key (/Users/(username)/.ssh/id_rsa): vagrantid_rsa 4 Enter passphrase (empty for no passphrase): 5 Enter same passphrase again: 6

linux配置ssh互信

                                                                       linux配置ssh互信 公钥认证的基本思想: 对信息的加密和解密采用不同的key,这对key分别称作private key和public key,其中,public key存放在欲登录的服务器上,而private key为特定的客户机所持有.当客户机向服务器发出建立安全连接的请求时,首先发送自己的public key,如果这个public key是被服务