Python 13:程序:堡垒机开发
1、需求
2、表结构
3、readme
4、目录结构
5、代码
6、测试样图
一、需求
功能需求:
1、所有的用户操作日志要保留在数据库中(先存到redis中防止数据断线丢失)
2、每个用户登录堡垒机后,只需要选择具体要访问的设置,就连接上了,不需要再输入目标机器的访问密码
3、允许用户对不同的目标设备有不同的访问权限,例:
1、对192.168.1.113有mysql 用户的权限
2、对192.168.1.105有root用户的权限
3、对192.168.1.100没任何权限
4、分组管理,即可以对设置进行分组,允许用户访问某组机器,但对组里的不同机器依然有不同的访问权限
二、表结构
三、readme
1 作者:zz 2 版本: 堡垒机 示例版本 v0.1 3 开发环境: python3.6 4 5 程序介绍 6 1、所有的用户操作日志要保留在数据库中(先存到redis中防止数据断线丢失) 7 2、每个用户登录堡垒机后,只需要选择具体要访问的设置,就连接上了,不需要再输入目标机器的访问密码 8 3、允许用户对不同的目标设备有不同的访问权限,例: 9 1、对192.168.1.113有mysql 用户的权限 10 2、对192.168.1.105有root用户的权限 11 3、对192.168.1.100没任何权限 12 4、分组管理,即可以对设置进行分组,允许用户访问某组机器,但对组里的不同机器依然有不同的访问权限 13 14 使用说明 15 python3 bin/run.py start_session #启动 16 python3 bin/run.py syncdb #创建表结构 17 python3 bin/run.py create_users -f share/examples/new_user.yml #创建堡垒机账户 18 python3 bin/run.py create_groups -f share/examples/new_groups.yml #创建用户组 19 python3 bin/run.py create_hosts -f share/examples/new_hosts.yml #创建远程主机 20 python3 bin/run.py create_bindhosts -f share/examples/new_bindhosts.yml #创建绑定主机 21 python3 bin/run.py create_remoteusers -f share/examples/new_remoteusers.yml #创建远程主机登录方式 22 python3 bin/run.py view_user_record # 审计用户操作命令
四、目录结构
五、代码
1、bin
1 import os,sys 2 3 BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 4 print(BASE_DIR) 5 sys.path.append(BASE_DIR) 6 7 if __name__ == '__main__': 8 from modules.actions import excute_from_command_line 9 excute_from_command_line(sys.argv)
2、conf
1 from modules import views 2 3 actions = { 4 'start_session': views.start_session, #启动s 5 'syncdb': views.syncdb, #创建表结构 6 'create_users': views.create_users, #创建堡垒机账户 7 'create_groups': views.create_groups, #创建用户组 8 'create_hosts': views.create_hosts, #创建远程主机 9 'create_bindhosts': views.create_bindhosts, #创建绑定主机 10 'create_remoteusers': views.create_remoteusers, #创建远程主机登录方式 11 'view_user_record': views.user_record_cmd # 审计用户操作命令 12 }
1 import os,sys 2 BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 3 DB_CONN ="mysql+pymysql://root:123456@192.168.1.113:3306/bljdb?charset=utf8"
3、modules
1 from conf import settings 2 from conf import action_registers 3 from modules import utils 4 5 def help_msg(): 6 '''help''' 7 print("\033[31;1mAvailable commands:\033[0m") 8 for key in action_registers.actions: 9 print("\t",key) 10 11 def excute_from_command_line(argvs): 12 if len(argvs) < 2: 13 help_msg() 14 exit() 15 if argvs[1] not in action_registers.actions: 16 utils.print_err("Command [%s] does not exist!" % argvs[1], quit=True) 17 action_registers.actions[argvs[1]](argvs[1:])
1 from modules import models 2 from modules.db_conn import engine,session 3 from modules.utils import print_err 4 5 def bind_hosts_filter(vals): 6 print('**>',vals.get('bind_hosts') ) #打印绑定主机信息 7 bind_hosts = session.query(models.BindHost).filter(models.Host.hostname.in_(vals.get('bind_hosts'))).all() #从数据库中查询对应绑定主机信息 8 if not bind_hosts: #如果不存在相应信息 9 print_err("none of [%s] exist in bind_host table." % vals.get('bind_hosts'),quit=True) #打印报错 10 return bind_hosts #返回对应bind_hosts信息 11 12 def user_profiles_filter(vals): 13 user_profiles = session.query(models.UserProfile).filter(models.UserProfile.username.in_(vals.get('user_profiles'))).all() #从数据库中查询堡垒机用户信息 14 if not user_profiles: #如果不存在相应信息 15 print_err("none of [%s] exist in user_profile table." % vals.get('user_profiles'),quit=True) #打印报错 16 return user_profiles #返回对应用户信息
1 from sqlalchemy import create_engine,Table 2 from sqlalchemy.orm import sessionmaker 3 from conf import settings 4 engine = create_engine(settings.DB_CONN) 5 # engine = create_engine(settings.DB_CONN,echo=True) 6 SessionCls = sessionmaker(bind=engine) #创建与数据库的会话session class ,注意,这里返回给session的是个class,不是实例 7 session = SessionCls()
1 #!/usr/bin/env python 2 3 # Copyright (C) 2003-2007 Robey Pointer <robeypointer@gmail.com> 4 # 5 # This file is part of paramiko. 6 # 7 # Paramiko is free software; you can redistribute it and/or modify it under the 8 # terms of the GNU Lesser General Public License as published by the Free 9 # Software Foundation; either version 2.1 of the License, or (at your option) 10 # any later version. 11 # 12 # Paramiko is distributed in the hope that it will be useful, but WITHOUT ANY 13 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 14 # A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 15 # details. 16 # 17 # You should have received a copy of the GNU Lesser General Public License 18 # along with Paramiko; if not, write to the Free Software Foundation, Inc., 19 # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. 20 21 import base64 22 import getpass 23 import os 24 import socket 25 import sys 26 import traceback 27 from paramiko.py3compat import input 28 import paramiko 29 try: 30 import interactive 31 except ImportError: 32 from . import interactive 33 # now, connect and use paramiko Client to negotiate SSH2 across the connection 34 try: 35 client = paramiko.SSHClient() 36 client.load_system_host_keys() 37 client.set_missing_host_key_policy(paramiko.WarningPolicy()) 38 print('*** Connecting...') 39 client.connect(hostname, port, username, password) 40 chan = client.invoke_shell() 41 print(repr(client.get_transport())) 42 print('*** Here we go!\n') 43 interactive.interactive_shell(chan) 44 chan.close() 45 client.close() 46 47 except Exception as e: 48 print('*** Caught exception: %s: %s' % (e.__class__, e)) 49 traceback.print_exc() 50 try: 51 client.close() 52 except: 53 pass 54 sys.exit(1)
1 # Copyright (C) 2003-2007 Robey Pointer <robeypointer@gmail.com> 2 # 3 # This file is part of paramiko. 4 # 5 # Paramiko is free software; you can redistribute it and/or modify it under the 6 # terms of the GNU Lesser General Public License as published by the Free 7 # Software Foundation; either version 2.1 of the License, or (at your option) 8 # any later version. 9 # 10 # Paramiko is distributed in the hope that it will be useful, but WITHOUT ANY 11 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 12 # A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 13 # details. 14 # 15 # You should have received a copy of the GNU Lesser General Public License 16 # along with Paramiko; if not, write to the Free Software Foundation, Inc., 17 # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. 18 19 20 import socket 21 import sys 22 from paramiko.py3compat import u 23 from modules import models 24 import datetime,time 25 import redis 26 # windows does not have termios... 27 try: 28 import termios 29 import tty 30 has_termios = True 31 except ImportError: 32 has_termios = False 33 def interactive_shell(chan,user_obj,bind_host_obj,cmd_caches,log_recording): 34 ''' 35 :param chan: 36 :param user_obj: 37 :param bind_host_obj: 主机 38 :param cmd_caches: 命令列表 39 :param log_recording: 日志记录 40 :return: 41 ''' 42 # 判断是否是windows shell 43 if has_termios: 44 posix_shell(chan,user_obj,bind_host_obj,cmd_caches,log_recording) 45 else: 46 windows_shell(chan) 47 def posix_shell(chan,user_obj,bind_host_obj,cmd_caches,log_recording): 48 ''' 49 :param chan: 50 :param user_obj: 51 :param bind_host_obj: 52 :param cmd_caches: 53 :param log_recording: 54 :return: 55 ''' 56 import select 57 oldtty = termios.tcgetattr(sys.stdin) 58 try: 59 tty.setraw(sys.stdin.fileno()) 60 tty.setcbreak(sys.stdin.fileno()) 61 chan.settimeout(0.0) 62 cmd = '' 63 tab_key = False 64 while True: 65 r, w, e = select.select([chan, sys.stdin], [], []) 66 if chan in r: 67 try: 68 x = u(chan.recv(1024)) 69 if tab_key: 70 if x not in ('\x07' , '\r\n'): 71 #print('tab:',x) 72 cmd += x 73 tab_key = False 74 if len(x) == 0: 75 sys.stdout.write('\r\n*** EOF\r\n') 76 # test for redis to mysql 77 break 78 sys.stdout.write(x) 79 sys.stdout.flush() 80 except socket.timeout: 81 pass 82 if sys.stdin in r: 83 x = sys.stdin.read(1) 84 if '\r' != x: 85 cmd +=x 86 else: 87 print('cmd->:',cmd) 88 user_record_cmd = user_obj.username + 'cmd' 89 pool = redis.ConnectionPool(host='192.168.1.100', port=6379) 90 user_record = [user_obj.id, bind_host_obj.id, 'cmd', cmd, 91 time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())] 92 r = redis.Redis(connection_pool=pool) 93 r.lpush(user_record_cmd, user_record) 94 cmd = '' #清空cmd 95 if '\t' == x: 96 tab_key = True 97 if len(x) == 0: 98 break 99 chan.send(x) 100 finally: 101 termios.tcsetattr(sys.stdin, termios.TCSADRAIN, oldtty) 102 103 # thanks to Mike Looijmans for this code 104 def windows_shell(chan): 105 import threading 106 sys.stdout.write("Line-buffered terminal emulation. Press F6 or ^Z to send EOF.\r\n\r\n") 107 def writeall(sock): 108 while True: 109 data = sock.recv(256) 110 if not data: 111 sys.stdout.write('\r\n*** EOF ***\r\n\r\n') 112 sys.stdout.flush() 113 break 114 sys.stdout.write(data.decode()) 115 sys.stdout.flush() 116 writer = threading.Thread(target=writeall, args=(chan,)) 117 writer.start() 118 try: 119 while True: 120 d = sys.stdin.read(1) 121 if not d: 122 break 123 chan.send(d) 124 except EOFError: 125 # user hit ^Z or F6 126 pass
1 from sqlalchemy import create_engine,Table 2 from sqlalchemy.ext.declarative import declarative_base 3 from sqlalchemy import Column, Integer, String,ForeignKey,UniqueConstraint,UnicodeText,DateTime 4 from sqlalchemy.orm import relationship 5 from sqlalchemy import or_,and_ 6 from sqlalchemy import func 7 from sqlalchemy_utils import ChoiceType,PasswordType 8 9 Base = declarative_base() #生成一个ORM 基类 10 11 #多对多关联 12 #多对多关联:绑定主机和用户组 13 BindHost2Group = Table('bindhost_2_group',Base.metadata, 14 Column('bindhost_id',ForeignKey('bind_host.id'),primary_key=True), 15 Column('group_id',ForeignKey('groups.id'),primary_key=True), 16 ) 17 #多对多关联:绑定主机和堡垒机用户 18 BindHost2UserProfile = Table('bindhost_2_userprofile',Base.metadata, 19 Column('bindhost_id',ForeignKey('bind_host.id'),primary_key=True), 20 Column('uerprofile_id',ForeignKey('user_profile.id'),primary_key=True), 21 ) 22 #多对多关联:用户组和堡垒机用户 23 Group2UserProfile = Table('group_2_userprofile',Base.metadata, 24 Column('userprofile_id',ForeignKey('user_profile.id'),primary_key=True), 25 Column('group_id',ForeignKey('groups.id'),primary_key=True), 26 ) 27 28 class BindHost(Base): 29 '''绑定主机(IP和用户联合唯一), 30 192.168.1.1 mysql 31 10.5.1.6 root''' 32 __tablename__ = 'bind_host' #表名 33 id = Column(Integer,primary_key=True,autoincrement=True) 34 host_id = Column(Integer,ForeignKey('host.id')) #外键关联host.id 35 remoteuser_id = Column(Integer,ForeignKey('remote_user.id')) #外键关联remote_user.id 36 37 host = relationship("Host") #通过host字段查询host表中相关数据 38 remoteuser = relationship("RemoteUser") #通过remoteuser字段查询remote_user表中相关数据 39 groups = relationship("Group",secondary=BindHost2Group,backref='bind_hosts') #group与bind_host多对多对应并且可以互查,对应关系存在表BindHost2Group中 40 user_profiles = relationship("UserProfile",secondary=BindHost2UserProfile,backref='bind_hosts') #user_profile与bind_host多对多对应并且可以互查,对应关系存在表BindHost2Group中 41 audit_logs = relationship('AuditLog') #通过audit_logs字段查询auditLog表中相关数据 42 __table_args__ = (UniqueConstraint('host_id', 'remoteuser_id', name='_bindhost_and_user_uc'),) # 联合唯一 43 44 def __repr__(self): 45 return "<BindHost(id='%s',name='%s',user='%s')>" % (self.id,self.host.hostname,self.remoteuser.username) 46 47 48 class UserProfile(Base): 49 '''堡垒机用户''' 50 __tablename__ = 'user_profile' 51 id = Column(Integer,primary_key=True,autoincrement=True) 52 username = Column(String(32),unique=True,nullable=False) 53 password = Column(String(128),unique=True,nullable=False) 54 groups = relationship('Group',secondary=Group2UserProfile,backref='user_profiles') #group与user_profile多对多对应并且可以互查,对应关系存在表Group2UserProfile中 55 audit_logs = relationship('AuditLog') #通过audit_logs字段查询auditLog表中相关数据 56 57 def __repr__(self): 58 return "<UserProfile(id='%s',username='%s')>" % (self.id,self.username) 59 60 class Group(Base): 61 '''用户组''' 62 __tablename__ = 'groups' 63 id = Column(Integer,primary_key=True) 64 name = Column(String(64),nullable=False,unique=True) 65 def __repr__(self): 66 return "<HostGroup(id='%s',name='%s')>" % (self.id,self.name) 67 68 class Host(Base): 69 '''远程主机''' 70 __tablename__ = 'host' 71 id = Column(Integer,primary_key=True,autoincrement=True) 72 hostname = Column(String(64),unique=True,nullable=False) 73 ip_addr = Column(String(128),unique=True,nullable=False) 74 port = Column(Integer,default=22) 75 bind_hosts = relationship("BindHost") #通过bind_host字段查询bind_host表中相关数据 76 def __repr__(self): 77 return "<Host(id='%s',hostname='%s')>" % (self.id,self.hostname) 78 79 class RemoteUser(Base): 80 '''远程用户''' 81 __tablename__ = 'remote_user' 82 AuthTypes = [ 83 ('ssh-passwd','SSH/Password'),# 第一个是存在数据库里的,第二个具体的值 84 ('ssh-key','SSH/KEY'), 85 ] 86 id = Column(Integer,primary_key=True,autoincrement=True) 87 auth_type = Column(ChoiceType(AuthTypes)) 88 username = Column(String(64),nullable=False) 89 password = Column(String(255)) 90 91 __table_args__ = (UniqueConstraint('auth_type', 'username','password', name='_user_passwd_uc'),) #联合唯一 92 93 def __repr__(self): 94 return "<RemoteUser(id='%s',auth_type='%s',user='%s')>" % (self.id,self.auth_type,self.username) 95 96 97 class AuditLog(Base): 98 '''用户操作日志''' 99 __tablename__ = 'audit_log' 100 id = Column(Integer,primary_key=True) 101 user_id = Column(Integer,ForeignKey('user_profile.id')) 102 bind_host_id = Column(Integer,ForeignKey('bind_host.id')) 103 action_choices = [ 104 (0,'CMD'), 105 (1,'Login'), 106 (2,'Logout'), 107 (3,'GetFile'), 108 (4,'SendFile'), 109 (5,'Exception'), 110 ] 111 action_choices2 = [ 112 (u'cmd',u'CMD'), 113 (u'login',u'Login'), 114 (u'logout',u'Logout'), 115 ] 116 action_type = Column(ChoiceType(action_choices2)) 117 #action_type = Column(String(64)) 118 cmd = Column(String(255)) 119 date = Column(DateTime) 120 user_profile = relationship("UserProfile") 121 bind_host = relationship("BindHost") 122 '''def __repr__(self): 123 return "<user=%s,host=%s,action=%s,cmd=%s,date=%s>" %(self.user_profile.username, 124 self.bind_host.host.hostname, 125 self.action_type, 126 self.date) 127 '''
import base64 import getpass import os import socket import sys import traceback from paramiko.py3compat import input from modules import models import datetime,time import redis import paramiko try: import interactive except ImportError: from . import interactive def ssh_login(user_obj,bind_host_obj,mysql_engine,log_recording): # now, connect and use paramiko Client to negotiate SSH2 across the connection ''' ssh登陆 :param user_obj: :param bind_host_obj: :param mysql_engine: 连接数据库 :param log_recording: 写日志记录 :return: ''' try: client = paramiko.SSHClient() client.load_system_host_keys() client.set_missing_host_key_policy(paramiko.WarningPolicy()) print('*** Connecting...') #client.connect(hostname, port, username, password) client.connect(bind_host_obj.host.ip_addr, bind_host_obj.host.port, bind_host_obj.remoteuser.username, bind_host_obj.remoteuser.password, timeout=30) cmd_caches = [] chan = client.invoke_shell() print(repr(client.get_transport())) print('*** Here we go!\n') # 连接redis pool = redis.ConnectionPool(host='192.168.1.100', port=6379) # 传一个命令列表给redis r = redis.Redis(connection_pool=pool) user_record = [user_obj.id, bind_host_obj.id, 'login', time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())] # 用户名做key前缀,避免冲突 key_name = str(user_obj.username)+'_login' r.lpush(key_name, user_record) interactive.interactive_shell(chan, user_obj, bind_host_obj, cmd_caches, log_recording) chan.close() client.close() # 数据库写入操作 login_record = r.lrange(key_name, 0, -1) login_redis_record = login_record[0].decode().replace('[', '').replace(']', '').split(',') log_item = models.AuditLog(user_id=login_redis_record[0], bind_host_id=login_redis_record[1], action_type='login', cmd='login', date=login_redis_record[3].replace("'", '')) cmd_caches.append(log_item) log_recording(user_obj, bind_host_obj, cmd_caches) user_record_cmd = user_obj.username+'cmd' cmd_redis_record = r.lrange(user_record_cmd, 0, -1) for i in cmd_redis_record: cmd_caches = [] v = i.decode().replace('[', '').replace(']', '').split(',') v2 = v[3].replace("'", '') print(v[0], v[1], v[2], v[3], v[4]) log_item = models.AuditLog(user_id=v[0], bind_host_id=v[1], action_type='cmd', cmd=v2, date=v[4].replace("'", '')) cmd_caches.append(log_item) log_recording(user_obj, bind_host_obj, cmd_caches) # 当退出的时候将redis的值写入到数据库并且清空redis logout_caches = [] logout_caches.append(models.AuditLog(user_id=user_obj.id, bind_host_id=bind_host_obj.id, action_type='logout', cmd='logout', date=datetime.datetime.now())) log_recording(user_obj, bind_host_obj, logout_caches) # 清空keys r.delete(key_name) r.delete(user_record_cmd) except Exception as e: print('*** Caught exception: %s: %s' % (e.__class__, e)) traceback.print_exc() try: client.close() except: pass sys.exit(1)
1 from conf import settings 2 import yaml 3 try: 4 from yaml import CLoader as Loader, CDumper as Dumper 5 except ImportError: 6 from yaml import Loader, Dumper 7 8 def print_err(msg,quit=False): 9 output = "\033[31;1mError: %s\033[0m" % msg 10 if quit: 11 exit(output) 12 else: 13 print(output) 14 15 16 def yaml_parser(yml_filename): 17 ''' 18 load yaml file and return 19 :param yml_filename: 20 :return: 21 ''' 22 #yml_filename = "%s/%s.yml" % (settings.StateFileBaseDir,yml_filename) 23 try: 24 yaml_file = open(yml_filename,'r') 25 data = yaml.load(yaml_file) 26 return data 27 except Exception as e: 28 print_err(e)
1 from modules import models 2 from modules.db_conn import engine,session 3 from modules.utils import print_err,yaml_parser 4 from modules import common_filters 5 from modules import ssh_login 6 def auth(): 7 '''登陆验证模块''' 8 count = 0 9 while count <3: #允许用户尝试次数 10 username = input("\033[32;1mUsername:\033[0m").strip() 11 if len(username) ==0:continue 12 password = input("\033[32;1mPassword:\033[0m").strip() 13 if len(password) ==0:continue 14 user_obj = session.query(models.UserProfile).filter(models.UserProfile.username==username, 15 models.UserProfile.password==password).first() #匹配用户名密码 16 if user_obj: #账号密码正确 17 return user_obj 18 else: #账号密码错误 19 print("wrong username or password, you have %s more chances." %(3-count-1)) 20 count +=1 21 else: 22 print_err("too many attempts.") 23 24 def welcome_msg(user): 25 '''欢迎界面''' 26 WELCOME_MSG = '''\033[32;1m 27 ------------- Welcome [%s] login LittleFinger ------------- 28 \033[0m'''% user.username 29 print(WELCOME_MSG) 30 31 def log_recording(user_obj,bind_host_obj,logs): 32 '''将用户操作存入数据库''' 33 print("\033[41;1m--logs:\033[0m",logs) 34 session.add_all(logs) 35 session.commit() 36 def start_session(argvs): 37 '''启动模块''' 38 print('going to start sesssion ') 39 user = auth() #登录验证 40 if user: 41 welcome_msg(user) 42 # print(user.bind_hosts) 43 # print(user.groups) 44 exit_flag = False 45 while not exit_flag: 46 if user.bind_hosts: #若用户关联了绑定主机 47 print('\033[32;1mz.\tungroupped hosts (%s)\033[0m' %len(user.bind_hosts) ) #打印用户绑定的未分组的主机个数 48 for index,group in enumerate(user.groups): #打印用户绑定的用户组 49 print('\033[32;1m%s.\t%s (%s)\033[0m' %(index,group.name, len(group.bind_hosts)) ) 50 choice = input("[%s]:" % user.username).strip() 51 if len(choice) == 0:continue 52 if choice == 'z': #打印未分组的绑定主机 53 print("------ Group: ungroupped hosts ------" ) 54 for index,bind_host in enumerate(user.bind_hosts): #提取主机名和ip 55 print(" %s.\t%s@%s(%s)"%(index, 56 bind_host.remoteuser.username, 57 bind_host.host.hostname, 58 bind_host.host.ip_addr, 59 )) 60 print("----------- END -----------" ) 61 while not exit_flag: 62 user_option = input("[(b)back, (q)quit, select host to login]:").strip() #选择功能 63 if len(user_option)==0:continue 64 if user_option == 'b':break 65 if user_option == 'q': 66 exit_flag=True 67 if user_option.isdigit(): #判断用户输入是否为数字 68 user_option = int(user_option) 69 print(user.bind_hosts) 70 print(len(user.bind_hosts)) 71 if user_option < len(user.bind_hosts) : #判断用户输入数字是否超出界限 72 print('host:',user.bind_hosts[user_option]) #打印host 73 print('audit log:',user.bind_hosts[user_option].audit_logs) #添加登陆日志 74 ssh_login.ssh_login(user,user.bind_hosts[user_option],session,log_recording) #调用ssh登陆模块登陆 75 elif choice.isdigit(): #打印绑定的用户组包含的主机 76 choice = int(choice) #输入为数字 77 if choice < len(user.groups): #判断输入是否超出界限 78 print("------ Group: %s ------" % user.groups[choice].name ) #打印包含的主机信息 79 for index,bind_host in enumerate(user.groups[choice].bind_hosts): #提取主机名和ip 80 print(" %s.\t%s@%s(%s)"%(index, 81 bind_host.remoteuser.username, 82 bind_host.host.hostname, 83 bind_host.host.ip_addr, 84 )) 85 print("----------- END -----------" ) 86 else: 87 print("no this option..") 88 while not exit_flag: 89 user_option = input("[(b)back, (q)quit, select host to login]:").strip()#选择功能 90 if len(user_option)==0:continue 91 if user_option == 'b':break 92 if user_option == 'q': 93 exit_flag=True 94 if user_option.isdigit(): #判断用户输入是否为数字 95 user_option = int(user_option) 96 if user_option < len(user.groups[choice].bind_hosts) : 97 print('host:',user.groups[choice].bind_hosts[user_option]) 98 print('audit log:',user.groups[choice].bind_hosts[user_option].audit_logs)#添加登陆日志 99 ssh_login.ssh_login(user,user.groups[choice].bind_hosts[user_option],session,log_recording) #调用ssh登陆模块登陆 100 101 def stop_server(argvs): 102 pass 103 104 def create_users(argvs): 105 '''创建堡垒机用户''' 106 if '-f' in argvs: #create_users -f .yml 通过yaml文件创建 107 user_file = argvs[argvs.index("-f") +1 ] #提取文件 108 else: 109 print_err("invalid usage, should be:\ncreateusers -f <the new users file>",quit=True) #格式不正确 110 source = yaml_parser(user_file) #将文件转化为字典 111 if source: #非空 112 for key,val in source.items():#提取值 113 print(key,val) #key:用户名 val:字典 114 obj = models.UserProfile(username=key,password=val.get('password')) #添加用户信息到数据库 115 if val.get('groups'): #若用户关联了用户组 116 groups = session.query(models.Group).filter(models.Group.name.in_(val.get('groups'))).all() #提取关联用户组信息 117 if not groups: #若组不存在 118 print_err("none of [%s] exist in group table." % val.get('groups'),quit=True) #打印信息 119 obj.groups = groups #添加用户与组关联 120 if val.get('bind_hosts'): #若存在堡垒机用户与绑定主机对应关系 121 bind_hosts = common_filters.bind_hosts_filter(val) #调用modules下的common_filters下的bind_hosts_filter函数来添加对应关系 122 obj.bind_hosts = bind_hosts #添加绑定主机对应关系 123 print(obj) 124 session.add(obj) #添加 125 session.commit() #提交 126 127 def create_groups(argvs): 128 '''创建用户组''' 129 if '-f' in argvs: #create_groups -f .yml 通过yaml文件创建 130 group_file = argvs[argvs.index("-f") +1 ] #提取文件 131 else: 132 print_err("invalid usage, should be:\ncreategroups -f <the new groups file>",quit=True) #格式不正确 133 source = yaml_parser(group_file) #将文件转化为字典 134 if source: #非空 135 for key,val in source.items():#提取值 136 print(key,val) #key:组名 val:关联关系 137 obj = models.Group(name=key) #添加组名到数据库 138 if val.get('bind_hosts'): #若存在绑定主机与用户组的对应关系 139 bind_hosts = common_filters.bind_hosts_filter(val) #调用modules下的common_filters下的bind_hosts_filter函数来添加对应关系 140 obj.bind_hosts = bind_hosts #添加绑定主机对应关系 141 142 if val.get('user_profiles'): 143 user_profiles = common_filters.user_profiles_filter(val) #调用modules下的common_filters下的user_profiles_filter函数来添加对应关系 144 obj.user_profiles = user_profiles #添加堡垒机用户和用户组的对应关系 145 session.add(obj) #添加 146 session.commit() #提交 147 148 def create_hosts(argvs): 149 '''创建远程主机''' 150 if '-f' in argvs: #create_hosts -f .yml 通过yaml文件创建 151 hosts_file = argvs[argvs.index("-f") +1 ] #提取文件 152 else: 153 print_err("invalid usage, should be:\ncreate_hosts -f <the new hosts file>",quit=True) #格式不正确 154 source = yaml_parser(hosts_file) #将文件转化为字典 155 if source: #非空 156 for key,val in source.items(): #提取值 157 print(key,val) #key:主机名 val:字典 158 obj = models.Host(hostname=key,ip_addr=val.get('ip_addr'), port=val.get('port') or 22) #添加入数据库 159 session.add(obj) 160 session.commit() #提交 161 162 def create_bindhosts(argvs): 163 '''创建绑定主机''' 164 if '-f' in argvs: #create_bindhosts -f .yml 通过yaml文件创建 165 bindhosts_file = argvs[argvs.index("-f") +1 ] #提取文件 166 else: 167 print_err("invalid usage, should be:\ncreate_hosts -f <the new bindhosts file>",quit=True) #格式不正确 168 source = yaml_parser(bindhosts_file) #将文件转化为字典 169 if source: #非空 170 for key,val in source.items(): #提取值 171 print(key,val) #key:bind val:字典 172 host_obj = session.query(models.Host).filter(models.Host.hostname==val.get('hostname')).first() #提取hostname 173 assert host_obj #断言 174 for item in val['remote_users']: #提取用户信息 175 print(item ) 176 assert item.get('auth_type') #断言非空 177 if item.get('auth_type') == 'ssh-passwd': #判断登录方式 178 remoteuser_obj = session.query(models.RemoteUser).filter( 179 models.RemoteUser.username==item.get('username'), 180 models.RemoteUser.password==item.get('password'), 181 ).first() #提取用户信息 182 else: 183 remoteuser_obj = session.query(models.RemoteUser).filter( 184 models.RemoteUser.username==item.get('username'), 185 models.RemoteUser.auth_type==item.get('auth_type'), 186 ).first() #提取用户信息 187 if not remoteuser_obj: #若用户不存在 188 print_err("RemoteUser obj %s does not exist." % item,quit=True ) #打印报错 189 bindhost_obj = models.BindHost(host_id=host_obj.id,remoteuser_id=remoteuser_obj.id) #添加绑定主机对应信息到数据库 190 session.add(bindhost_obj) #添加 191 #for groups this host binds to 192 if source[key].get('groups'): #若存在绑定主机与用户组的对应关系 193 group_objs = session.query(models.Group).filter(models.Group.name.in_(source[key].get('groups') )).all() #提取组信息 194 assert group_objs #若组存在 195 print('groups:', group_objs) 196 bindhost_obj.groups = group_objs #添加绑定用户和组的对应关系 197 #for user_profiles this host binds to 198 if source[key].get('user_profiles'): #若存在绑定主机和堡垒机用户的对应关系 199 print(source[key].get('user_profiles')) #提取堡垒机用户信息 200 userprofile_objs = session.query(models.UserProfile).filter(models.UserProfile.username.in_(source[key].get('user_profiles'))).all() #提取对应用户信息 201 print(userprofile_objs) 202 assert userprofile_objs #若用户信息存在 203 print("userprofiles:",userprofile_objs) 204 bindhost_obj.user_profiles = userprofile_objs #添加用户信息与绑定主机对应关系 205 #print(bindhost_obj) 206 session.commit() #提交 207 208 def create_remoteusers(argvs): 209 '''创建远程主机登录方式''' 210 if '-f' in argvs: #create_remoteusers -f .yml 通过yaml文件创建 211 remoteusers_file = argvs[argvs.index("-f") +1 ] #提取文件 212 else: 213 print_err("invalid usage, should be:\ncreate_remoteusers -f <the new remoteusers file>",quit=True) #格式不正确 214 source = yaml_parser(remoteusers_file) #将文件转化为字典 215 if source: #非空 216 for key,val in source.items(): #提取值 217 print(key,val) #key:用户 val:字典 218 obj = models.RemoteUser(username=val.get('username'),auth_type=val.get('auth_type'),password=val.get('password')) #添加入数据库 219 session.add(obj) 220 session.commit() #提交 221 222 def syncdb(argvs): 223 print("Syncing DB....") 224 models.Base.metadata.create_all(engine) #创建所有表结构 225 226 def user_record_cmd(argvs): 227 '''查看操作记录 ''' 228 print('going to start view record') 229 user = auth() 230 # 默认root可以查所有人的记录 231 if user.username == 'root': 232 print('welcome %s ' % user.username) 233 exit_flag = False 234 # 用户对象 235 user_obj = session.query(models.UserProfile).filter().all() 236 # 循环查看堡垒机用户操作 237 while not exit_flag: 238 for user_profile_list in user_obj: 239 # 打印堡垒机用户,根据堡垒机用户ID选择其管辖的机器并打印日志 240 print("%s.\t%s" % (user_profile_list.id, user_profile_list.username)) 241 choice = input("[%s]:" % user.username).strip() 242 for user_profile_list in user_obj: 243 if str(choice) == str(user_profile_list.id): 244 if user_profile_list.bind_hosts: 245 # 显示未分组的机器 246 print('\033[32;1mz.\tungroupped hosts (%s)\033[0m' % len(user_profile_list.bind_hosts)) 247 else: 248 print(' no binding groups ') 249 for index, group in enumerate(user_profile_list.groups): 250 print('\033[32;1m%s.\t%s (%s)\033[0m' % (index, group.name, len(group.bind_hosts))) 251 choice = input("[%s]:" % user.username).strip() 252 if len(choice) == 0:continue 253 if choice == 'z': #打印未分组的绑定主机 254 print("------ Group: ungroupped hosts ------" ) 255 for index,bind_host in enumerate(user_profile_list.bind_hosts): #提取主机名和ip 256 print(" %s.\t%s@%s(%s)"%(index, 257 bind_host.remoteuser.username, 258 bind_host.host.hostname, 259 bind_host.host.ip_addr, 260 )) 261 print("----------- END -----------" ) 262 while not exit_flag: 263 user_option = input("[(b)back, (q)quit, select host to login]:").strip() 264 if len(user_option) == 0: 265 continue 266 if user_option == 'b': 267 break 268 if user_option == 'q': 269 exit_flag = True 270 if user_option.isdigit(): 271 user_option = int(user_option) 272 if user_option < len(user_profile_list.bind_hosts): 273 # print('host:', user_profile_list.host_groups[choice].bind_hosts[user_option]) 274 data = \ 275 session.query(models.AuditLog).filter( 276 models.AuditLog.user_id == user_profile_list.id, 277 models.AuditLog.bind_host_id == user_profile_list. 278 bind_hosts[user_option].id).all() 279 if data: 280 for index, i in enumerate(data): 281 # print(index, i.date, i.cmd) 282 print(index, i.date, i.cmd.encode().decode('unicode-escape')) 283 else: 284 print('no record in host:', user_profile_list.groups[choice]. 285 bind_hosts[user_option]) 286 elif choice.isdigit(): # 打印分组的机器 287 choice = int(choice) 288 if choice < len(user_profile_list.groups): 289 print("------ Group: %s ------" % user_profile_list.groups[choice].name) 290 for index, bind_host in enumerate(user_profile_list.groups[choice].bind_hosts): 291 print(" %s.\t%s@%s(%s)" % (index, 292 bind_host.remoteuser.username, 293 bind_host.host.hostname, 294 bind_host.host.ip_addr, 295 )) 296 print("----------- END -----------") 297 # host selection 选择机器去查看操作信息 298 while not exit_flag: 299 user_option = input("[(b)back, (q)quit, select host to login]:").strip() 300 if len(user_option) == 0: 301 continue 302 if user_option == 'b': 303 break 304 if user_option == 'q': 305 exit_flag = True 306 if user_option.isdigit(): 307 user_option = int(user_option) 308 if user_option < len(user_profile_list.groups[choice].bind_hosts): 309 # print('host:', user_profile_list.host_groups[choice].bind_hosts[user_option]) 310 data = \ 311 session.query(models.AuditLog).filter( 312 models.AuditLog.user_id == user_profile_list.id, 313 models.AuditLog.bind_host_id == user_profile_list.groups[choice]. 314 bind_hosts[user_option].id).all() 315 if data: 316 for index, i in enumerate(data): 317 # redis 写入value的时候带有了\t \n 等需要转义 318 # 第一个注释从数据库里读注释的这种不能转移\t, 319 # 第二个和现行的俩种中文转义有些问题 320 # print(i.user_id, i.bind_host_id, i.action_type, i.cmd, i.date) 321 # print(i.user_id, i.bind_host_id, i.action_type, 322 # codecs.getdecoder("unicode_escape")(i.cmd)[0], i.date) 323 # print(i.user_id, i.bind_host_id, i.action_type, 324 # i.cmd.encode().decode('unicode-escape'), i.date) 325 print(index, i.date, i.cmd) 326 # print(index, i.date, i.cmd.encode().decode('unicode-escape')) 327 else: 328 print('no record in host:', user_profile_list.groups[choice]. 329 bind_hosts[user_option]) 330 # 其他人只能查自己的操作记录 331 else: 332 exit_flag = False 333 while not exit_flag: 334 if user.bind_hosts: #若用户关联了绑定主机 335 print('\033[32;1mz.\tungroupped hosts (%s)\033[0m' %len(user.bind_hosts) ) #打印用户绑定的未分组的主机个数 336 for index,group in enumerate(user.groups): #打印用户绑定的用户组 337 print('\033[32;1m%s.\t%s (%s)\033[0m' %(index,group.name, len(group.bind_hosts)) ) 338 339 choice = input("[%s]:" % user.username).strip() 340 if len(choice) == 0:continue 341 if choice == 'z': #打印未分组的绑定主机 342 print("------ Group: ungroupped hosts ------" ) 343 for index,bind_host in enumerate(user.bind_hosts): #提取主机名和ip 344 print(" %s.\t%s@%s(%s)"%(index, 345 bind_host.remoteuser.username, 346 bind_host.host.hostname, 347 bind_host.host.ip_addr, 348 )) 349 print("----------- END -----------" ) 350 while not exit_flag: 351 user_option = input("[(b)back, (q)quit, select host to login]:").strip() 352 if len(user_option) == 0: 353 continue 354 if user_option == 'b': 355 break 356 if user_option == 'q': 357 exit_flag = True 358 if user_option.isdigit(): 359 user_option = int(user_option) 360 if user_option < len(user.bind_hosts): 361 # print('host:', user_profile_list.host_groups[choice].bind_hosts[user_option]) 362 data = \ 363 session.query(models.AuditLog).filter( 364 models.AuditLog.user_id == user.id, 365 models.AuditLog.bind_host_id == user.bind_hosts[user_option].id).all() 366 if data: 367 for index, i in enumerate(data): 368 # print(index, i.date, i.cmd) 369 print(index, i.date, i.cmd.encode().decode('unicode-escape')) 370 else: 371 print('no record in host:', user.groups[choice]. 372 bind_hosts[user_option]) 373 374 elif choice.isdigit(): # 打印分组的机器 375 choice = int(choice) 376 if choice < len(user.groups): 377 print("------ Group: %s ------" % user.groups[choice].name) 378 for index, bind_host in enumerate(user.groups[choice].bind_hosts): 379 print(" %s.\t%s@%s(%s)" % (index, 380 bind_host.remoteuser.username, 381 bind_host.host.hostname, 382 bind_host.host.ip_addr, 383 )) 384 print("----------- END -----------") 385 # host selection 选择机器去查看操作信息 386 while not exit_flag: 387 user_option = input("[(b)back, (q)quit, select host to login]:").strip() 388 if len(user_option) == 0: 389 continue 390 if user_option == 'b': 391 break 392 if user_option == 'q': 393 exit_flag = True 394 if user_option.isdigit(): 395 user_option = int(user_option) 396 if user_option < len(user.groups[choice].bind_hosts): 397 # print('host:', user_profile_list.host_groups[choice].bind_hosts[user_option]) 398 data = \ 399 session.query(models.AuditLog).filter( 400 models.AuditLog.user_id == user.id, 401 models.AuditLog.bind_host_id == user.groups[choice]. 402 bind_hosts[user_option].id).all() 403 if data: 404 for index, i in enumerate(data): 405 # print(index, i.date, i.cmd) 406 print(index, i.date, i.cmd.encode().decode('unicode-escape')) 407 else: 408 print('no record in host:', user.groups[choice]. 409 bind_hosts[user_option])
4、share/examples
1 bind1: 2 hostname: server1 3 remote_users: 4 - user2: 5 username: root 6 auth_type: ssh-key 7 #password: 123 8 - user1: 9 username: mysql 10 auth_type: ssh-passwd 11 password: 123456 12 groups: 13 - bj_group 14 user_profiles: 15 - aa 16 17 bind2: 18 hostname: server2 19 remote_users: 20 - user0: 21 username: root 22 auth_type: ssh-passwd 23 password: 123456 24 groups: 25 - bj_group 26 - sh_group 27 28 user_profiles: 29 - bb
1 web_servers: 2 bind_hosts: 3 - h1 4 - h2 5 user_profiles: 6 - alex 7 8 db_servers: 9 user_profiles: 10 - jack 11 - alex 12 - rain
1 h1: 2 ip_addr: 192.168.1.1 3 port: 20022 4 5 h2: 6 ip_addr: 192.168.1.100 7 port: 22 8 9 10 mysql: 11 ip_addr: 192.168.43.5 12 port: 3306 13 14 server1: 15 ip_addr: 192.168.1.1 16 port: 30000 17 18 server2: 19 ip_addr: 10.4.4.22 20 21 root: 22 ip_addr: 192.168.1.112
1 user0: 2 auth_type: ssh-passwd 3 username: root 4 password: 123456 5 6 user1: 7 auth_type: ssh-passwd 8 username: mysql 9 password: 123456 10 11 user2: 12 auth_type: ssh-key 13 username: root 14 #password: abc!23
1 aa: 2 password: 111 3 groups: 4 - web_servers 5 - db_servers 6 bind_hosts: 7 - h1 8 - h2 9 - h3 10 bb: 11 password: 222 12 13 cc: 14 password: 333 15 bind_hosts: 16 - h1 17 - h3
六、测试样图
1、创建表结构
2、添加数据
3、登陆操作
4、命令审计
a、root用户(审计所有用户)
b、个人用户(审计当前用户操作命令)
来源:https://www.cnblogs.com/hy0822/p/9427179.html