用paramiko寫堡壘機


paramiko

paramiko模塊,基於SSH用於連接遠程服務器並執行相關操作。

基本用法

SSHClient

基於用戶名密碼連接:

基礎用法:

import paramiko
   
# 創建SSH對象
ssh = paramiko.SSHClient()
# 允許連接不在know_hosts文件中的主機
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
# 連接服務器
ssh.connect(hostname='c1.salt.com', port=22, username='wupeiqi', password='123')
   
# 執行命令
stdin, stdout, stderr = ssh.exec_command('ls')
# 獲取命令結果
result = stdout.read()
   
# 關閉連接
ssh.close()

SSHClient 封裝 Transport

import paramiko

transport = paramiko.Transport(('hostname', 22))
transport.connect(username='wupeiqi', password='123')

ssh = paramiko.SSHClient()
ssh._transport = transport

stdin, stdout, stderr = ssh.exec_command('df')
print stdout.read()

transport.close()

由此我們可以看出來,ssh執行命令時,我們可以使用sshclient transport兩種方式來實現

基於公鑰密鑰連接:

基礎用法:

import paramiko
  
private_key = paramiko.RSAKey.from_private_key_file('/home/auto/.ssh/id_rsa')
  
# 創建SSH對象
ssh = paramiko.SSHClient()
# 允許連接不在know_hosts文件中的主機
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
# 連接服務器
ssh.connect(hostname='c1.salt.com', port=22, username='wupeiqi', key=private_key)
  
# 執行命令
stdin, stdout, stderr = ssh.exec_command('df')
# 獲取命令結果
result = stdout.read()
  
# 關閉連接
ssh.close()

封裝transport:

import paramiko

private_key = paramiko.RSAKey.from_private_key_file('/home/auto/.ssh/id_rsa')

transport = paramiko.Transport(('hostname', 22))
transport.connect(username='wupeiqi', pkey=private_key)

ssh = paramiko.SSHClient()
ssh._transport = transport

stdin, stdout, stderr = ssh.exec_command('df')

transport.close()

SFTPClient

用於連接遠程服務器並執行上傳下載

基於用戶名密碼上傳下載:

import paramiko
  
transport = paramiko.Transport(('hostname',22))
transport.connect(username='wupeiqi',password='123')
  
sftp = paramiko.SFTPClient.from_transport(transport)
# 將location.py 上傳至服務器 /tmp/test.py
sftp.put('/tmp/location.py', '/tmp/test.py')
# 將remove_path 下載到本地 local_path
sftp.get('remove_path', 'local_path')
  
transport.close()

基於公鑰密鑰上傳下載:

import paramiko
  
private_key = paramiko.RSAKey.from_private_key_file('/home/auto/.ssh/id_rsa')
  
transport = paramiko.Transport(('hostname', 22))
transport.connect(username='wupeiqi', pkey=private_key )
  
sftp = paramiko.SFTPClient.from_transport(transport)
# 將location.py 上傳至服務器 /tmp/test.py
sftp.put('/tmp/location.py', '/tmp/test.py')
# 將remove_path 下載到本地 local_path
sftp.get('remove_path', 'local_path')
  
transport.close()

有此看出,如果只做上傳下載方式的話,我們只能使用transport,其實無論是ssh,還是sftp,都是調用了transport,基於socket實現的

生產需求:上傳某文件並覆蓋

demo:

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import paramiko
import uuid

class SSHConnection(object):

    def __init__(self, host='172.16.103.191', port=22, username='wupeiqi',pwd='123'):
        self.host = host
        self.port = port
        self.username = username
        self.pwd = pwd
        self.__k = None

    def create_file(self):
        file_name = str(uuid.uuid4())
        with open(file_name,'w') as f:
            f.write('sb')
        return file_name

    def run(self):
        self.connect()
        self.upload('/home/wupeiqi/tttttttttttt.py')
        self.rename('/home/wupeiqi/tttttttttttt.py', '/home/wupeiqi/ooooooooo.py)
        self.close()

    def connect(self):
        transport = paramiko.Transport((self.host,self.port))
        transport.connect(username=self.username,password=self.pwd)
        self.__transport = transport

    def close(self):

        self.__transport.close()

    def upload(self,target_path):
        # 連接,上傳
        file_name = self.create_file()

        sftp = paramiko.SFTPClient.from_transport(self.__transport)
        # 將location.py 上傳至服務器 /tmp/test.py
        sftp.put(file_name, target_path)

    def rename(self, old_path, new_path):

        ssh = paramiko.SSHClient()
        ssh._transport = self.__transport
        # 執行命令
        cmd = "mv %s %s" % (old_path, new_path,)
        stdin, stdout, stderr = ssh.exec_command(cmd)
        # 獲取命令結果
        result = stdout.read()

    def cmd(self, command):
        ssh = paramiko.SSHClient()
        ssh._transport = self.__transport
        # 執行命令
        stdin, stdout, stderr = ssh.exec_command(command)
        # 獲取命令結果
        result = stdout.read()
        return result
        


ha = SSHConnection()
ha.run()

對於更多限制命令,需要在系統中設置:

位置:/etc/sudoers,代碼:

Defaults    requiretty
Defaults:cmdb    !requiretty

堡壘機

執行流程:

  1. 管理員為用戶在服務器上創建賬號(將公鑰放置服務器,或者使用用戶名密碼)
  2. 用戶登陸堡壘機,輸入堡壘機用戶名密碼,現實當前用戶管理的服務器列表
  3. 用戶選擇服務器,並自動登陸
  4. 執行操作並同時將用戶操作記錄

需要注意的是,如果想實現用戶登錄后直接操作,需要配置下堡壘機用戶家目錄的.bashrc文件:

/usr/bin/env python $PATH/s7.py
logout

將這兩行假如即可.

看下實現方式:

簡單調用:

import paramiko
import sys
import os
import socket
import select
import getpass

tran = paramiko.Transport(('192.168.4.193', 22,))
tran.start_client()
tran.auth_password('root', '7ujm8ik,')

# 打開一個通道
chan = tran.open_session()
# 獲取一個終端
chan.get_pty()
# 激活器
chan.invoke_shell()

#########
# 利用sys.stdin,肆意妄為執行操作
# 用戶在終端輸入內容,並將內容發送至遠程服務器
# 遠程服務器執行命令,並將結果返回
# 用戶終端顯示內容
#########

交互操作(無tab)

import paramiko
import sys
import os
import socket
import select
import getpass
from paramiko.py3compat import u
 
tran = paramiko.Transport(('10.211.55.4', 22,))
tran.start_client()
tran.auth_password('wupeiqi', '123')
 
# 打開一個通道
chan = tran.open_session()
# 獲取一個終端
chan.get_pty()
# 激活器
chan.invoke_shell()
 
while True:
    # 監視用戶輸入和服務器返回數據
    # sys.stdin 處理用戶輸入
    # chan 是之前創建的通道,用於接收服務器返回信息
    readable, writeable, error = select.select([chan, sys.stdin, ],[],[],1)
    if chan in readable:
        try:
            x = u(chan.recv(1024))
            if len(x) == 0:
                print('\r\n*** EOF\r\n')
                break
            sys.stdout.write(x)
            sys.stdout.flush()
        except socket.timeout:
            pass
    if sys.stdin in readable:
        inp = sys.stdin.readline()
        chan.sendall(inp)
 
chan.close()
tran.close()

但此次我們會發現,沒有tab補全,跟我們真是在shell里執行命令還是略有差距的,那么來第二個.

交互操作(有tab功能)

#!/usr/bin/env python
# -*-coding=utf-8-*-
# Auther:ccorz Mail:ccniubi@163.com Blog:http://www.cnblogs.com/ccorz/
# GitHub:https://github.com/ccorzorz

import paramiko
import sys
import os
import socket
import select
import getpass
import termios
import tty
from paramiko.py3compat import u

tran = paramiko.Transport(('192.168.4.193', 22,))
tran.start_client()
tran.auth_password('root', '7ujm8ik,')

# 打開一個通道
chan = tran.open_session()
# 獲取一個終端
chan.get_pty()
# 激活器
chan.invoke_shell()


# 獲取原tty屬性
oldtty = termios.tcgetattr(sys.stdin)
try:
    # 為tty設置新屬性
    # 默認當前tty設備屬性:
    #   輸入一行回車,執行
    #   CTRL+C 進程退出,遇到特殊字符,特殊處理。

    # 這是為原始模式,不認識所有特殊符號
    # 放置特殊字符應用在當前終端,如此設置,將所有的用戶輸入均發送到遠程服務器
    tty.setraw(sys.stdin.fileno())
    chan.settimeout(0.0)

    while True:
        # 監視 用戶輸入 和 遠程服務器返回數據(socket)
        # 阻塞,直到句柄可讀
        r, w, e = select.select([chan, sys.stdin], [], [], 1)
        if chan in r:
            try:
                x = u(chan.recv(1024))
                if len(x) == 0:
                    print('\r\n*** EOF\r\n')
                    break
                sys.stdout.write(x)
                sys.stdout.flush()
            except socket.timeout:
                pass
        if sys.stdin in r:
            x = sys.stdin.read(1)
            if len(x) == 0:
                break
            chan.send(x)

finally:
    # 重新設置終端屬性,必須設置,否則再次登錄后無法使用
    termios.tcsetattr(sys.stdin, termios.TCSADRAIN, oldtty)


chan.close()
tran.close()

生產需求

在生產中,我們還需要更多的需求:

  1. 有日志記錄,我們可以把管理員的每一條記錄都記錄到數據庫中
  2. tab補全時,記錄的中間會有空格之類的需要處理

我們來看下怎么實現:

import paramiko
import sys
import os
import socket
import getpass

# from paramiko.py3compat import u

# windows does not have termios...
try:
    import termios
    import tty
    has_termios = True
except ImportError:
    has_termios = False

def interactive_shell(chan):
    if has_termios:
        posix_shell(chan)
    else:
        windows_shell(chan)


def posix_shell(chan):
    import select

    oldtty = termios.tcgetattr(sys.stdin)
    try:
        tty.setraw(sys.stdin.fileno())
        tty.setcbreak(sys.stdin.fileno())
        chan.settimeout(0.0)
        f = open('handle.log','a+')
        tab_flag = False
        temp_list = []
        while True:
            r, w, e = select.select([chan, sys.stdin], [], [])
            if chan in r:
                try:
                    x = chan.recv(1024)
                    if len(x) == 0:
                        sys.stdout.write('\r\n*** EOF\r\n')
                        break
                    if tab_flag:
                        if x.startswith('\r\n'):
                            pass
                        else:
                            f.write(x)
                            f.flush()
                        tab_flag = False
                    sys.stdout.write(x)
                    sys.stdout.flush()
                except socket.timeout:
                    pass
            if sys.stdin in r:
                x = sys.stdin.read(1)
                if len(x) == 0:
                    break
                if x == '\t':
                    tab_flag = True
                else:
                    f.write(x)
                    f.flush()
                chan.send(x)

    finally:
        termios.tcsetattr(sys.stdin, termios.TCSADRAIN, oldtty)


def windows_shell(chan):
    import threading

    sys.stdout.write("Line-buffered terminal emulation. Press F6 or ^Z to send EOF.\r\n\r\n")

    def writeall(sock):
        while True:
            data = sock.recv(256)
            if not data:
                sys.stdout.write('\r\n*** EOF ***\r\n\r\n')
                sys.stdout.flush()
                break
            sys.stdout.write(data)
            sys.stdout.flush()

    writer = threading.Thread(target=writeall, args=(chan,))
    writer.start()

    try:
        while True:
            d = sys.stdin.read(1)
            if not d:
                break
            chan.send(d)
    except EOFError:
        # user hit ^Z or F6
        pass


def run():
    # 獲取當前登錄用戶
"""

    host_list = [
        {'host': "192.168.11.139", 'username': 'oldboy', 'pwd': "123"},
        {'host': "192.168.11.138", 'username': 'oldboy', 'pwd': "123"},
        {'host': "192.168.11.137", 'username': 'oldboy', 'pwd': "123"},
    ]
    for item in enumerate(host_list, 1):
        print(item['host'])

    num = raw_input('序號:')
    sel_host = host_list[int(num) -1]
    hostname = sel_host['host']
    username = sel_host['username']
    pwd = sel_host['pwd']
    print(hostname,username,pwd)
"""

    tran = paramiko.Transport((hostname, 22,))
    tran.start_client()
    tran.auth_password(username, pwd)
    # 打開一個通道
    chan = tran.open_session()
    # 獲取一個終端
    chan.get_pty()
    # 激活器
    chan.invoke_shell()

    interactive_shell(chan)

    chan.close()
    tran.close()


if __name__ == '__main__':
    run()

銀角究極版

珍藏下吧:

import paramiko
import sys
import os
import socket
import getpass

from paramiko.py3compat import u

# windows does not have termios...
try:
    import termios
    import tty
    has_termios = True
except ImportError:
    has_termios = False


def interactive_shell(chan):
    if has_termios:
        posix_shell(chan)
    else:
        windows_shell(chan)


def posix_shell(chan):
    import select

    oldtty = termios.tcgetattr(sys.stdin)
    try:
        tty.setraw(sys.stdin.fileno())
        tty.setcbreak(sys.stdin.fileno())
        chan.settimeout(0.0)
        log = open('handle.log', 'a+', encoding='utf-8')
        flag = False
        temp_list = []
        while True:
            r, w, e = select.select([chan, sys.stdin], [], [])
            if chan in r:
                try:
                    x = u(chan.recv(1024))
                    if len(x) == 0:
                        sys.stdout.write('\r\n*** EOF\r\n')
                        break
                    if flag:
                        if x.startswith('\r\n'):
                            pass
                        else:
                            temp_list.append(x)
                        flag = False
                    sys.stdout.write(x)
                    sys.stdout.flush()
                except socket.timeout:
                    pass
            if sys.stdin in r:
                x = sys.stdin.read(1)
                import json

                if len(x) == 0:
                    break

                if x == '\t':
                    flag = True
                else:
                    temp_list.append(x)
                if x == '\r':
                    log.write(''.join(temp_list))
                    log.flush()
                    temp_list.clear()
                chan.send(x)

    finally:
        termios.tcsetattr(sys.stdin, termios.TCSADRAIN, oldtty)


def windows_shell(chan):
    import threading

    sys.stdout.write("Line-buffered terminal emulation. Press F6 or ^Z to send EOF.\r\n\r\n")

    def writeall(sock):
        while True:
            data = sock.recv(256)
            if not data:
                sys.stdout.write('\r\n*** EOF ***\r\n\r\n')
                sys.stdout.flush()
                break
            sys.stdout.write(data)
            sys.stdout.flush()

    writer = threading.Thread(target=writeall, args=(chan,))
    writer.start()

    try:
        while True:
            d = sys.stdin.read(1)
            if not d:
                break
            chan.send(d)
    except EOFError:
        # user hit ^Z or F6
        pass


def run():
    default_username = getpass.getuser()
    username = input('Username [%s]: ' % default_username)
    if len(username) == 0:
        username = default_username


    hostname = input('Hostname: ')
    if len(hostname) == 0:
        print('*** Hostname required.')
        sys.exit(1)

    tran = paramiko.Transport((hostname, 22,))
    tran.start_client()

    default_auth = "p"
    auth = input('Auth by (p)assword or (r)sa key[%s] ' % default_auth)
    if len(auth) == 0:
        auth = default_auth

    if auth == 'r':
        default_path = os.path.join(os.environ['HOME'], '.ssh', 'id_rsa')
        path = input('RSA key [%s]: ' % default_path)
        if len(path) == 0:
            path = default_path
        try:
            key = paramiko.RSAKey.from_private_key_file(path)
        except paramiko.PasswordRequiredException:
            password = getpass.getpass('RSA key password: ')
            key = paramiko.RSAKey.from_private_key_file(path, password)
        tran.auth_publickey(username, key)
    else:
        pw = getpass.getpass('Password for %s@%s: ' % (username, hostname))
        tran.auth_password(username, pw)

    # 打開一個通道
    chan = tran.open_session()
    # 獲取一個終端
    chan.get_pty()
    # 激活器
    chan.invoke_shell()

    interactive_shell(chan)

    chan.close()
    tran.close()


if __name__ == '__main__':
    run()

數據庫模型

其實堡壘機的難點是在數據庫的設計上

#!/usr/bin/env python
# -*-coding=utf-8-*-
# Auther:ccorz Mail:ccniubi@163.com Blog:http://www.cnblogs.com/ccorz/
# GitHub:https://github.com/ccorzorz

from sqlalchemy import create_engine, and_, or_, func, Table
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String, ForeignKey, UniqueConstraint, DateTime
from  sqlalchemy.orm import sessionmaker, relationship

engine = create_engine("mysql+pymysql://root:7ujm8ik,@192.168.4.193:3306/test13", max_overflow=5)
Base = declarative_base()  # 生成一個SqlORM 基類


class UserProfile2HostUser(Base):
    __tablename__ = 'userprofile_2_hostuser'
    id = Column(Integer, primary_key=True, autoincrement=True)
    userprofile_id = Column(Integer, ForeignKey('user_profile.id'),primary_key=True)
    hostuser_id = Column(Integer, ForeignKey('host_user.id'),primary_key=True)
    # userprofile = relationship('UserProfile',secondary=lambda :)


class Host(Base):
    __tablename__ = 'host'
    id = Column(Integer, primary_key=True, autoincrement=True)
    hostname = Column(String(64), unique=True, nullable=False)
    ip_addr = Column(String(128), unique=True, nullable=False)
    port = Column(Integer, default=22)


class HostUser(Base):
    __tablename__ = 'host_user'
    id = Column(Integer, primary_key=True, autoincrement=True)
    AuthTypes = [
        (u'ssh-passwd', u'SSH/Password'),
        (u'ssh-key', u'SSH/KEY'),
    ]
    auth_type = Column(String(64))
    username = Column(String(64), nullable=False)
    password = Column(String(255))
    host_id = Column(Integer, ForeignKey('host.id'))
    host = relationship('Host', backref='uu')
    __table_args__ = (UniqueConstraint(u'host_id', u'username', name='_host_username_uc'),)

class Group(Base):
    __tablename__ = 'group'
    id = Column(Integer, primary_key=True, autoincrement=True)
    name = Column(String(64), unique=True, nullable=False)

#
# obj = session.query(HostUser.username,HostUser.password,Host.hostname,Host.port).join(Host).filter(HostUser.id == 1).first()
# (用戶名,密碼,主機名,端口)

class UserProfile(Base):
    __tablename__ = 'user_profile'
    id = Column(Integer, primary_key=True)
    username = Column(String(64), unique=True, nullable=False)
    # 存密碼感覺沒什么卵用
    # password = Column(String(255),nullable=False)
    # 如果是一個人只能在一個組下
    group_id = Column(Integer, ForeignKey('group.id'))
    #需要這么加secondary,否則插入數據時會報錯
    host_list = relationship('HostUser', secondary=lambda :UserProfile2HostUser.__table__, backref='userprofiles')


"""
# 輸入用戶名和密碼:
#
obj = session.query(UserProfile).filter(username=輸入的用戶名, password=輸入的密碼).first()
if not obj:
	# 堡壘機登錄用戶對象
	# 輸入這個人的所有機器
	obj.host_list # 當前堡壘機登錄用戶,所有的服務器用戶名
	#
	for item  in obj.host_list:
		# item,是一個HostUser對象
		item.password,item.username,
		# item.host 對象,host對象
		item.host.hostname,item.host.port
	# item 目標機器HostUser對象
	host_obj = input(:...)
	session.add(AuditLog(userprofile_id=obj.id,hostuser_id = host_obj.id, "ifconfig"))
"""


class Log(Base):
    __tablename__ = 'log'
    id = Column(Integer, primary_key=True)
    userprofile_id = Column(Integer, ForeignKey('user_profile.id'))
    hostuser_id = Column(Integer, ForeignKey('host_user.id'))
    cmd = Column(String(255))
    date = Column(DateTime)


# class Session:
#     session = None
#     def __init__(self):
#         self.engine = create_engine("mysql+pymysql://root:7ujm8ik,@192.168.4.193:3306/test13", max_overflow=5)
#         self.ss = sessionmaker(bind=self.engine)
#         self.obj = ss()
#         self.Session.session = obj
#
#     @classmethod
#     def instance(cls):
#         if not cls.session:
#             cls()
#         return cls.session


# ss = Session()
# 定義初始化數據庫函數
def init_db():
    Base.metadata.create_all(engine)


# init_db()

# 刪除數據庫函數
def drop_db():
    Base.metadata.drop_all(engine)


# drop_db()


# 實例化數據庫操作對象為session
Session = sessionmaker(bind=engine)
ss = Session()
#
# ss.add_all([
#     Group(id=1, name='DBA'),
#     Group(id=2, name='SA')
# ])
#
# ss.add_all([
#     UserProfile(id=1,username='chengc',group_id=2),
#     UserProfile(id=2,username='root',group_id=2)
# ])
#
# ss.add_all([
#     Host(id=1,hostname='test',ip_addr='192.168.4.193',port=22),
#     Host(id=2,hostname='zhongrt1',ip_addr='223.202.101.164',port=43228)
# ])
#
# ss.add_all([
#     HostUser(id=1,auth_type='pwd',username='root',password='7ujm8ik,',host_id=1),
#     HostUser(id=2,auth_type='pwd',username='root',password='asdf',host_id=2)
# ])
#
# ss.add_all([
#     UserProfile2HostUser(userprofile_id=1,hostuser_id=1),
#     UserProfile2HostUser(userprofile_id=2,hostuser_id=1)
# ])
# #
# ss.add_all([Group(id=3,name='SB')])
ss.commit()

特別注意:如果使用relationship虛擬關系時,需要添加secondary=類名.table,否則插入數據時會報錯!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM