paramiko 堡垒机

本文介绍如何使用Python的Paramiko模块实现堡垒机的功能,包括通过用户名密码和公钥密钥的方式进行连接,执行命令,上传下载文件等操作,并提供详细的代码示例。

用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()
posted on 2017-07-21 17:33 秦瑞It行程实录 阅读( ...) 评论( ...) 编辑 收藏

转载于:https://www.cnblogs.com/ruiy/p/7218858.html

### 堡垒机的配置、使用与原理 #### 一、堡垒机的基本原理 堡垒机是一种专门用于管理和控制用户访问内部网络资源的安全设备,通常部署在企业或组织的网络中。它充当一个“跳板”或“网关”,用户必须通过堡垒机才能访问内部服务器或敏感资源[^3]。堡垒机的核心功能包括身份验证、权限控制和操作审计。 堡垒机的工作原理是通过逻辑隔离的方式将运维人员与目标设备隔离开来,建立从“运维人员 -> 堡垒机用户账号 -> 授权 -> 目标设备账号 -> 目标设备”的管理模式。这种模式解决了操作权限控制和行为审计的问题,同时支持对加密协议和图形协议等进行审计[^5]。 #### 二、堡垒机的部署方式 堡垒机分为商业堡垒机和开源堡垒机。开源堡垒机由于其透明性和灵活性,逐渐成为未来的主流选择。Jumpserver 是全球首款完全开源的堡垒机,符合 4A(账号管理、认证管理、授权管理和审计管理)的专业运维审计系统。其 GitHub Star 数超过 1.1 万,显示了其广泛的受欢迎程度[^1]。 #### 三、堡垒机的配置方法 基于 Docker 的 Jumpserver 堡垒机搭建是一个常见的实践案例。在完成系统部署后,需要对系统进行一些基础配置。这些配置主要包括以下内容: 1. **添加资产**:将需要管理的服务器或设备作为资产添加到堡垒机中。 2. **创建管理用户**:为资产配置管理员账户,以便堡垒机能够通过这些账户连接到目标设备。 3. **创建普通用户**:为运维人员创建堡垒机用户账号。 4. **授权管理**:定义普通用户对资产的访问权限,确保每个用户只能访问其被授权的资源。 5. **日志审计**:启用操作记录功能,确保所有用户的操作都被完整记录并可追溯[^2]。 #### 四、堡垒机的工作流程 堡垒机的工作流程可以概括为以下几个阶段: 1. 用户通过堡垒机的身份验证机制登录系统。 2. 根据预设的权限规则,堡垒机授予用户对特定资产的访问权限。 3. 用户的所有操作都会被实时监控和记录,以便后续审计和分析。 4. 堡垒机会对网络环境中的系统状态、安全事件和网络活动进行集中收集和监控,提供统一的报警、记录和处理机制[^4]。 ```python # 示例代码:通过堡垒机连接目标服务器的简化逻辑 import paramiko def connect_to_target_server(fortress_host, fortress_port, target_host, target_port, username, password): # 创建SSH客户端 ssh = paramiko.SSHClient() ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) # 连接到堡垒机 ssh.connect(fortress_host, port=fortress_port, username=username, password=password) # 通过堡垒机连接目标服务器 transport = ssh.get_transport() dest_addr = (target_host, target_port) local_addr = ('127.0.0.1', 10022) # 可选本地端口 channel = transport.open_channel("direct-tcpip", dest_addr, local_addr) # 使用通道连接目标服务器 target_ssh = paramiko.SSHClient() target_ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) target_ssh.connect(target_host, port=target_port, username=username, password=password, sock=channel) return target_ssh ``` #### 五、总结 堡垒机作为网络安全的重要组成部分,提供了强大的身份验证、权限控制和操作审计功能。无论是商业堡垒机还是开源堡垒机(如 Jumpserver),都可以帮助企业有效提升运维安全性和合规性。配置堡垒机时,需重点关注资产添加、用户管理、权限分配和日志审计等关键环节。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值