Python之路——堡垒机原理及其简单实现

1 堡垒机基本概述  

其从功能上讲,它综合了核心系统运维和安全审计管控两大主干功能,从技术实现上讲,通过切断终端计算机对网络和服务器资源的直接访问,而采用协议代理的方式,接管了终端计算机对网络和服务器的访问。形象地说,终端计算机对目标的访问,均需要经过运维安全审计的翻译。打一个比方,运维安全审计扮演着看门者的工作,所有对网络设备和服务器的请求都要从这扇大门经过。因此运维安全审计能够拦截非法访问,和恶意攻击,对不合法命令进行命令阻断,过滤掉所有对目标设备的非法访问行为,并对内部人员误操作和非法操作进行审计监控,以便事后责任追踪。一个好的运维审计堡垒机产品应实现对服务器、网络设备、安全设备等核心资产的运维管理账号的集中管理、集中认证和授权,通过单点登录,提供对操作行为的精细化管理和审计,达到运维管理简单、方便、可靠的目的。堡垒机具备的两点要求:
   (1)管理方便
  应提供一套简单直观的账号管理、授权管理策略,管理员可快速方便地查找某个用户,查询修改访问权限;同时用户能够方便的通过登录堡垒机对自己的基本信息进行管理,包括账号、口令等进行修改更新。
  (2)可扩展
  当进行新系统建设或扩容时,需要增加新的设备到堡垒机时,系统应能方便的增加设备数量和设备种类。

2 堡垒机的概念和种类 

  “堡垒”一词的含义是指用于防守的坚固建筑物或比喻难于攻破的事物,因此从字面的意思来看“堡垒机”是指用于防御攻击的计算机。在实际应用中堡垒机又被称为“堡垒主机”,是一个主机系统,其自身通常经过了一定的加固,具有较高的安全性,可抵御一定的攻击,其作用主要是将需要保护的信息系统资源与安全威胁的来源进行隔离,从而在被保护的资源前面形成一个坚固的“堡垒”,并且在抵御威胁的同时又不影响普通用户对资源的正常访问。
   基于其应用场景,堡垒机可分为两种类型:
     2.1 网关型堡垒机
  网关型的堡垒机被部署在外部网络和内部网络之间,其本身不直接向外部提供服务而是作为进入内部网络的一个检查点,用于提供对内部网络特定资源的安全访问控制。这类堡垒机不提供路由功能,将内外网从网络层隔离开来,因此除非授权访问外还可以过滤掉一些针对内网的来自应用层以下的攻击,为内部网络资源提供了一道安全屏障。但由于此类堡垒机需要处理应用层的数据内容,性能消耗很大,所以随着网络进出口处流量越来越大,部署在网关位置的堡垒机逐渐成为了性能瓶颈,因此网关型的堡垒机逐渐被日趋成熟的防火墙、UTM、IPS、网闸等安全产品所取代。
    2.2 运维审计型堡垒机
  运维审计型堡垒机,有时也被称作“内控堡垒机”,这种类型的堡垒机也是当前应用最为普遍的一种。运维审计型堡垒机的原理与网关型堡垒机类似,但其部署位置与应用场景不同且更为复杂。运维审计型堡垒机被部署在内网中的服务器和网络设备等核心资源的前面,对运维人员的操作权限进行控制和操作行为审计;运维审计型堡垒机既解决了运维人员权限难以控制的混乱局面,又可对违规操作行为进行控制和审计,而且由于运维操作本身不会产生大规模的流量,堡垒机不会成为性能的瓶颈,所以堡垒机作为运维操作审计的手段得到了快速发展。

3 堡垒机运维操作审计的工作原理  

作为运维操作审计手段的堡垒机的核心功能是用于实现对运维操作人员的权限控制与操作行为审计。
  3.1 主要技术思路
  如何实现对运维人员的权限控制与审计呢?堡垒机必须能够截获运维人员的操作,并能够分析出其操作的内容。堡垒机的部署方式,确保它能够截获运维人员的所有操作行为,分析出其中的操作内容以实现权限控制和行为审计的目的,同时堡垒机还采用了应用代理的技术。
    运维审计型堡垒机对于运维操作人员相当于一台代理服务器(Proxy Server),其工作流程如下图所示:

Python之路——堡垒机原理及其简单实现

(1)运维人员在操作过程中首先连接到堡垒机,然后向堡垒机提交操作请求;
   (2)该请求通过堡垒机的权限检查后,堡垒机的应用代理模块将代替用户连接到目标设备完成该操作,之后目标设备将操作结果返回给堡垒机,最后堡垒机再将操作结果返回给运维人员。
   通过这种方式,堡垒机逻辑上将运维人员与目标设备隔离开来,建立了从“运维人员->堡垒机用户账号->授权->目标设备账号->目标设备”的管理模式,解决操作权限控制和行为审计。
   3.2 工作原理简介
   下面就简单介绍一下堡垒机运维操作审计的工作原理,其工作原理示意图如下:

Python之路——堡垒机原理及其简单实现

在实际使用场景中堡垒机的使用人员通常可分为管理人员、运维操作人员、审计人员三类用户。
  管理员最重要的职责是根据相应的安全策略和运维人员应有的操作权限来配置堡垒机的安全策略。堡垒机管理员登录堡垒机后,堡垒机内部“策略管理”组件负责与管理员进行交互,并将管理员输入的安全策略存储到堡垒机内部的策略配置库中。
 “应用代理”组件是堡垒机的核心,负责中转运维操作用户的操作并与堡垒机内部其他组件进行交互。“应用代理”组件收到运维人员的操作请求后调用“策略管理”组件对该操作行为进行核查,核查依据便是管理员已经配置好的策略配置库,如此次操作不符合安全策略,“应用代理”组件将拒绝该操作行为的执行。
  运维人员的操作行为通过“策略管理”组件的核查之后,“应用代理”组件则代替运维人员连接目标设备完成相应操作,并将操作结果返回给对应的运维操作人员;同时此次操作过程被提交给堡垒机内部的“审计模块”,然后此次操作过程被记录到审计日志数据库中。
  最后当需要调查运维人员的历史操作记录时,由审计员登录堡垒机进行查询,然后“审计模块”从审计日志数据库中读取相应日志记录并展示在审计员交互界面上。

   3.3 技术基础

以下代码其实是paramiko源码包里interactive.py的内容,适用于交互执行,前两种模式适用于linux,模式三适用于windows。

(1)模式1—操作linux

 #!/usr/bin/env python
#-*- coding:utf-8 -*- import paramiko
import os
import sys
import select
import socket tran = paramiko.Transport(('192.168.1.175', 22,)) #创建连接对象
tran.start_client() '''
#使用密钥认证
default_path = os.path.join(os.environ['root'], '.ssh', 'id_rsa') #获取秘钥路径
key = paramiko.RSAKey.from_private_key_file(default_path)
tran.auth_publickey('root', key)
'''
#通过用户名和密码认证
tran.auth_password('root', '')
chan = tran.open_session()# 打开一个通道
chan.get_pty()# 获取终端
chan.invoke_shell()# 激活一个交互式命令行会话 '''
# 利用sys.stdin,肆意妄为执行操作
# 用户在终端输入内容,并将内容发送至远程服务器
# 远程服务器执行命令,并将结果返回
# 用户终端显示内容
'''
while True:
#监视用户输入和服务器返回数据
#sys.stdin处理用户输入
#chan是之前创建的通道,用于接收服务器返回信息
#监听chan、终端,这里把chan也当做了文件描述符进行监听
readable, writeable, error = select.select([chan, sys.stdin],[],[],1)
#只要输入发生变化,那么chan、stdin其中之一就会发生变化或者两者都变化
if chan in readable: #捕获服务端变化
try:
x = chan.recv(1024) #接收数据,发送接收数据也是基于socket
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() #关闭chan通道
tran.close() #关闭连接

实例代码

上面的例子中,输入一行命令只有输入回车键后,sys.stdin才能捕获到,默认的终端也是这样设计的。

 #!/usr/bin/env python
#-*- coding:utf-8 -*- import paramiko
import os
import sys
import select
import socket tran = paramiko.Transport(('192.168.1.175', 22,)) #创建连接对象
tran.start_client() '''
#使用密钥认证
default_path = os.path.join(os.environ['root'], '.ssh', 'id_rsa')
key = paramiko.RSAKey.from_private_key_file(default_path)
tran.auth_publickey('root', key)
''' tran.auth_password('root', '111111!') #通过密码认证
chan = tran.open_session()# 打开一个通道
chan.get_pty()# 获取一个终端
chan.invoke_shell()# 激活交互式命令行终端 '''
# 利用sys.stdin,肆意妄为执行操作
# 用户在终端输入内容,并将内容发送至远程服务器
# 远程服务器执行命令,并将结果返回
# 用户终端显示内容
''' log = open('record.log','ab') #打开一个文件记录用户的输入 while True:
# 监视用户输入和服务器返回数据
# sys.stdin 处理用户输入
# chan 是之前创建的通道,用于接收服务器返回信息
readable, writeable, error = select.select([chan, sys.stdin, ],[],[],1) #监听 if chan in readable: #捕获远程服务器变化
try:
x = chan.recv(1024) #接收数据
if len(x) == 0:
log.close() #关闭文件
break
sys.stdout.write(x)#内容输出到终端
sys.stdout.flush()
except socket.timeout:
pass
if sys.stdin in readable: #捕获终端输入
inp = sys.stdin.readline() #读取用户输入
log.write(inp) #记录命令
chan.sendall(inp)#发送命令 chan.close()
tran.close()

记录操作命令代码

(2)模式2—操作linux

首先我们要做的就是修改终端模式:把原来的命令处理方式(即以换行符为命令的结尾),改为输入一个字符就发送服务器执行,同时支持对特殊字符的处理。

 #!/usr/bin/env python
#-*- coding:utf-8 -*- import paramiko
import os
import sys
import select
import socket
import termios
import tty tran = paramiko.Transport(('192.168.1.175', 22,))
tran.start_client() '''
#使用密钥认证
default_path = os.path.join(os.environ['root'], '.ssh', 'id_rsa')
key = paramiko.RSAKey.from_private_key_file(default_path)
tran.auth_publickey('root', key)
'''
tran.auth_password('root', '') #通过用户名密码认证
chan = tran.open_session()# 打开一个通道
chan.get_pty()# 获取一个终端
chan.invoke_shell()# 激活交互式终端 '''
# 利用sys.stdin,肆意妄为执行操作
# 用户在终端输入内容,并将内容发送至远程服务器
# 远程服务器执行命令,并将结果返回
# 用户终端显示内容
'''
# 获取原tty属性
oldtty = termios.tcgetattr(sys.stdin)
try:
# 为tty设置新属性
# 默认当前tty设备属性:
# 输入一行回车,执行
# CTRL+C进程退出,遇到特殊字符,特殊处理。
# 这是为原始模式,不认识所有特殊符号
# 放置特殊字符应用在当前终端,如此设置,将所有的用户输入均发送到远程服务器
tty.setraw(sys.stdin.fileno()) #把tty更换为LINUX原始模式
chan.settimeout(0.0) while True:
# 监视用户输入和远程服务器返回数据(socket)
# 阻塞,直到句柄可读
r, w, e = select.select([chan, sys.stdin], [], [], 1)
if chan in r:
try:
x = 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()

修改终端模式后代码

记录操作命令日志,并且不记录tab输入

 #!/usr/bin/env python
#-*- coding:utf-8 -*- import paramiko
import os
import sys
import select
import socket
import termios
import tty tran = paramiko.Transport(('192.168.1.175', 22,))
tran.start_client() '''
#使用密钥认证
default_path = os.path.join(os.environ['root'], '.ssh', 'id_rsa')
key = paramiko.RSAKey.from_private_key_file(default_path)
tran.auth_publickey('root', key)
'''
tran.auth_password('root', '') #通过用户名密码认证
chan = tran.open_session() #打开一个通道
chan.get_pty() #获取一个终端
chan.invoke_shell() #激活 '''
#利用sys.stdin,肆意妄为执行操作
#用户在终端输入内容,并将内容发送至远程服务器
#远程服务器执行命令,并将结果返回
#用户终端显示内容
'''
#获取原tty属性
oldtty = termios.tcgetattr(sys.stdin) try:
#为tty设置新属性
#默认当前tty设备属性:
#输入一行回车,执行
#CTRL+C进程退出,遇到特殊字符,特殊处理。 #这是为原始模式,不认识所有特殊符号
#放置特殊字符应用在当前终端,如此设置,将所有的用户输入均发送到远程服务器
tty.setraw(sys.stdin.fileno()) #更换为LINUX原始模式
chan.settimeout(0.0)
#打开文件
user_log = open('record.log','ab') while True:
# 监视用户输入和远程服务器返回数据
# 阻塞,直到句柄可读
r, w, e = select.select([chan, sys.stdin], [], [], 1)
if chan in r:
try:
x = chan.recv(1024)
if len(x) == 0:
user_log.close()
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
if x == '\t': #判断用户的是否为tab如果为tab将不记录
pass
else:
user_log.write(x)#如果用户输入的命令保存至日志
chan.send(x) finally:
# 重新设置终端属性
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, oldtty)
chan.close()
tran.close()

记录操作命令代码

(3)模式三—操作windows

 #!/usr/bin/env python
#-*- coding:utf-8 -*- import paramiko
import sys
import threading tran = paramiko.Transport(('192.168.1.175', 22,))
tran.start_client() '''
#使用密钥认证
default_path = os.path.join(os.environ['root'], '.ssh', 'id_rsa')
key = paramiko.RSAKey.from_private_key_file(default_path)
tran.auth_publickey('root', key)
'''
tran.auth_password('root', 'nihao123!') #通过姓名密码认证
chan = tran.open_session()# 打开一个通道
chan.get_pty()# 获取一个终端
chan.invoke_shell()# 激活 '''
# 利用sys.stdin,肆意妄为执行操作
# 用户在终端输入内容,并将内容发送至远程服务器
# 远程服务器执行命令,并将结果返回
# 用户终端显示内容
'''
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,)) #创建了一个线程,去执行writeall方法,参数为chan(建立的SSH连接)
writer.start() try:
while True: #主线程循环
d = sys.stdin.read(1) #输入一个字符发送一个
if not d:
break
chan.send(d)
except EOFError:
pass chan.close()
tran.close()

实例代码

待续....

参考资料:

http://sec.chinabyte.com/480/12202480.shtml

上一篇:hiho一下21周 线段树的区间修改 离散化


下一篇:python练习程序(c100经典例11)