堡垒机
一、作业需求:
1.业务需求
兼顾业务安全目标与用户体验,堡垒机部署后,不应使用户访问业务系统的访问变的复杂,否则工作将很难推进,因为没人喜欢改变现状,尤其是改变后生活变得更艰难
保证堡垒机稳定安全运行, 没有100%的把握,不要上线任何新系统,即使有100%把握,也要做好最坏的打算,想好故障预案
2.功能需求
所有的用户操作日志要保留在数据库中
每个用户登录堡垒机后,只需要选择具体要访问的设置,就连接上了,不需要再输入目标机器的访问密码
允许用户对不同的目标设备有不同的访问权限,例:
对10.0.2.34 有mysql 用户的权限
对192.168.3.22 有root用户的权限
对172.33.24.55 没任何权限
分组管理,即可以对设置进行分组,允许用户访问某组机器,但对组里的不同机器依然有不同的访问权限
二、表结构图
三、
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
一、作业需求: 1.业务需求 兼顾业务安全目标与用户体验,堡垒机部署后,不应使用户访问业务系统的访问变的复杂,否则工作将很难推进,因为没人喜欢改变现状,尤其是改变后生活变得更艰难 保证堡垒机稳定安全运行, 没有100%的把握,不要上线任何新系统,即使有100%把握,也要做好最坏的打算,想好故障预案 2.功能需求 所有的用户操作日志要保留在数据库中 每个用户登录堡垒机后,只需要选择具体要访问的设置,就连接上了,不需要再输入目标机器的访问密码 允许用户对不同的目标设备有不同的访问权限,例: 对10.0.2.34 有mysql 用户的权限 对192.168.3.22 有root用户的权限 对172.33.24.55 没任何权限 分组管理,即可以对设置进行分组,允许用户访问某组机器,但对组里的不同机器依然有不同的访问权限 二、博客地址:https://www.cnblogs.com/catepython/p/9177109.html 三、运行环境 操作系统:Win10 Python:3.6.4rcl Pycharm:2017.3.4
四、程序架构图
五、核心模块
bin目录
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#-*-coding:utf-8 -*- # Author: D.Gray import os,sys BASE_DESC = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) #print(BASE_DESC) sys.path.append(BASE_DESC) from modules.actions import excute_from_command_line excute_from_command_line(sys.argv)
conf目录
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#-*-coding:utf-8 -*- # Author: D.Gray from modules import views actions = { 'start_session': views.start_session, # 连接server # 'stop': views.stop_server, 'syncdb': views.syncdb, # 同步数据 'create_users': views.create_users, # 创建users 'create_groups': views.create_groups, # 创建组 'create_hosts': views.create_hosts, # 创建主机 'create_bindhosts': views.create_bindhosts, # 创建绑定关系 'create_remoteusers': views.create_remoteusers, # 创建远程用户 'view_user_record': views.user_record_cmd # 查看用户操作命令 }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#-*-coding:utf-8 -*- # Author: D.Gray import sqlalchemy import os,sys BASE_DESC = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) #print(BASE_DESC) sys.path.append(BASE_DESC) CONN = 'mysql+pymysql://root:admin1988@localhost/mychine?charset=utf8'
databases目录
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#-*-coding:utf-8 -*- # Author: D.Gray import sqlalchemy from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import relationship from sqlalchemy_utils import ChoiceType from sqlalchemy import Column,Integer,String,ForeignKey,UniqueConstraint,Table,Text,DateTime from conf.setting import CONN Base = declarative_base() user_m2m_bind = Table( 'user_m2m_bind',Base.metadata, Column('user_profile_id',Integer,ForeignKey('user_profile.id')), Column('bind_host_id',Integer,ForeignKey('bind_host.id')) ) bind_m2m_group = Table( 'bind_m2m_group',Base.metadata, Column('group_id',Integer,ForeignKey('group.group_id')), Column('bind_host_id',Integer,ForeignKey('bind_host.id')) ) user_m2m_group = Table( 'user_m2m_group',Base.metadata, Column('group_id',Integer,ForeignKey('group.group_id')), Column('user_id',Integer,ForeignKey('user_profile.id')) ) class Host(Base): __tablename__ = 'host' host_id = Column(Integer,primary_key=True) host_name = Column(String(32),unique=True) IP = Column(String(32),nullable=False,unique=True) port = Column(Integer,default=22) def __repr__(self): return '<名称:【%s】 IP:【%s】 port:【%s】>'%(self.host_name,self.IP,self.port) class Group(Base): __tablename__ = 'group' group_id = Column(Integer,primary_key=True) group_name = Column(String(32),nullable=False,unique=True) group_bind = relationship('BindHost',secondary = 'bind_m2m_group',backref = 'groups_key') def __repr__(self): return '<组名:【%s】>'%(self.group_name) class RemUser(Base): ''' 远程登录用户 ''' __tablename__ = 'rem_user' __table_args__ = (UniqueConstraint('auth_type','username','password',name = 'rems_uc'),) id = Column(Integer,primary_key=True) Auth_types = [ ('ssh-password','SSH/Password'), ('ssh-key','SSH/Key'), ] auth_type = Column(ChoiceType(Auth_types)) username = Column(String(32),nullable=False) password = Column(String(32)) def __repr__(self): return '<名称:【%s】 密码:【%s】 验证方式:【%s】>'%(self.username,self.password,self.auth_type) class BindHost(Base): ''' 此表是用来实现操控主机IP 和 登录用户 之间的绑定关系 IP 远程登录名 192.168.111.128 root 192.168.111.129 admin_kyo host_id remuser_id ''' __tablename__ = 'bind_host' __table_args__ = (UniqueConstraint('remuser_id','host_id',name = 'binds_uc'),) id = Column(Integer,primary_key=True) remuser_id = Column(Integer,ForeignKey('rem_user.id'),nullable=False) host_id = Column(Integer,ForeignKey('host.host_id'),nullable=False) bind_hosts = relationship('Host',backref = 'bind_hosts') bind_remusers = relationship('RemUser',backref = 'bind_remusers') def __repr__(self): return '<IP:【%s】 远程登录名:【%s】>'\ %(self.bind_hosts.IP,self.bind_remusers.username) class UserProfile(Base): ''' 堡垒机用户 ''' __tablename__ = 'user_profile' id = Column(Integer,primary_key=True) user_name = Column(String(32),nullable=False) password = Column(String(32),nullable=False) profile_bind = relationship('BindHost',secondary = 'user_m2m_bind',backref = 'user_profiles') profile_group = relationship('Group',secondary = 'user_m2m_group',backref = 'profile_groups') def __repr__(self): return '<名称:【%s】 密码:【%s】>'\ %(self.user_name,self.password) class AuditLog(Base): ''' 用户操作日志表 ''' __tablename__ = 'audit_log' id = Column(Integer, primary_key=True) user_id = Column(Integer, ForeignKey('user_profile.id')) bind_host_id = Column(Integer, ForeignKey('bind_host.id')) action_choices = [ (u'cmd', u'CMD'), (u'login', u'Login'), (u'logout', u'Logout'), ] action_type = Column(ChoiceType(action_choices)) # 命令可能存的数值更大 # cmd = Column(String(255)) cmd = Column(Text(65535)) date = Column(DateTime) user_profile = relationship("UserProfile") bind_host = relationship("BindHost")
modules目录
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#-*-coding:utf-8 -*- # Author: D.Gray from conf import action_registers from modules import utils def help_msg(): ''' 帮助函数 print help msgs :return: ''' print("\033[31;1mAvailable commands:\033[0m") for key in action_registers.actions: print("\t", key) def excute_from_command_line(argvs): ''' 命令执行函数 print :param argvs: :return: ''' if len(argvs) < 2: help_msg() exit() if argvs[1] not in action_registers.actions: utils.print_err("Command [%s] does not exist!" % argvs[1], quit=True) # utils 工具箱 action_registers.actions[argvs[1]](argvs[1:])
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#-*-coding:utf-8 -*- # Author: D.Gray from database import create_table from modules.db_conn import engine, session from modules.utils import print_err def bind_hosts_filter(vals): ''' :param vals: :return: ''' print('**>', vals.get('bind_hosts')) bind_hosts = session.query(create_table.BindHost).\ filter(create_table.Host.host_name.in_(vals.get('bind_hosts'))).all() if not bind_hosts: print_err("none of [%s] exist in bind_host table." % vals.get('bind_hosts'), quit=True) return bind_hosts def user_profiles_filter(vals): ''' :param vals: :return: ''' user_profiles = session.query(create_table.UserProfile).filter(create_table.UserProfile.user_name. in_(vals.get('user_profiles'))).all() if not user_profiles: print_err("none of [%s] exist in user_profile table." % vals.get('user_profiles'), quit=True) return user_profiles
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#-*-coding:utf-8 -*- # Author: D.Gray from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker from conf import setting engine = create_engine(setting.CONN) # 创建与数据库的会话session class ,注意,这里返回给session的是个class,不是实例 SessionCls = sessionmaker(bind=engine) session = SessionCls()
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#-*-coding:utf-8 -*- # Author: D.Gray # Copyright (C) 2003-2007 Robey Pointer <robeypointer@gmail.com> # # This file is part of paramiko. # # Paramiko is free software; you can redistribute it and/or modify it under the # terms of the GNU Lesser General Public License as published by the Free # Software Foundation; either version 2.1 of the License, or (at your option) # any later version. # # Paramiko is distributed in the hope that it will be useful, but WITHOUT ANY # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR # A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # details. # # You should have received a copy of the GNU Lesser General Public License # along with Paramiko; if not, write to the Free Software Foundation, Inc., # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. import socket import sys from paramiko.py3compat import u from database import create_table # from modules.views import log_recording import datetime import redis import time # windows does not have termios... try: import termios import tty has_termios = True except ImportError: has_termios = False def interactive_shell(chan, user_obj, bind_host_obj, cmd_caches, log_recording): ''' :param chan: :param user_obj: :param bind_host_obj: 主机 :param cmd_caches: 命令列表 :param log_recording: 日志记录 :return: ''' # 判断是否是windows shell if has_termios: posix_shell(chan, user_obj, bind_host_obj, cmd_caches, log_recording) else: windows_shell(chan) def posix_shell(chan, user_obj, bind_host_obj, cmd_caches, log_recording): ''' :param chan: :param user_obj: :param bind_host_obj: :param cmd_caches: :param log_recording: :return: ''' import select oldtty = termios.tcgetattr(sys.stdin) try: tty.setraw(sys.stdin.fileno()) tty.setcbreak(sys.stdin.fileno()) chan.settimeout(0.0) cmd = '' tab_key = False while True: r, w, e = select.select([chan, sys.stdin], [], []) if chan in r: try: x = u(chan.recv(1024)) if tab_key: if x not in ('\x07', '\r\n'): # print('tab:',x) cmd += x tab_key = False if len(x) == 0: sys.stdout.write('\r\n*** EOF\r\n') # test for redis to mysql break sys.stdout.write(x) sys.stdout.flush() except socket.timeout: pass if sys.stdin in r: x = sys.stdin.read(1) if '\r' != x: cmd += x else: user_record_cmd = user_obj.username + '_user_record' pool = redis.ConnectionPool(host='localhost', port=22) user_record = [user_obj.id, bind_host_obj.id, 'cmd', cmd, time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())] r = redis.Redis(connection_pool=pool) r.lpush(user_record_cmd, user_record) cmd = '' # 最后用户退出的时候取出来log_item 列表循环写入数据库 if '\t' == x: tab_key = True if len(x) == 0: break chan.send(x) finally: termios.tcsetattr(sys.stdin, termios.TCSADRAIN, oldtty) # thanks to Mike Looijmans for this code def windows_shell(chan): ''' :param chan: :return: ''' 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.decode()) 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
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#-*-coding:utf-8 -*- # Author: D.Gray import base64 import getpass import os import socket import sys import traceback from paramiko.py3compat import input from database import create_table import redis import datetime import time import paramiko try: import interactive except ImportError: from . import interactive def ssh_login(user_obj, bind_host_obj, mysql_engine, log_recording): ''' ssh登陆 :param user_obj: :param bind_host_obj: :param mysql_engine: 连接数据库 :param log_recording: 写日志记录 :return: ''' # now, connect and use paramiko Client to negotiate SSH2 across the connection try: client = paramiko.SSHClient() client.load_system_host_keys() client.set_missing_host_key_policy(paramiko.WarningPolicy()) print('*** Connecting...') client.connect(bind_host_obj.host.ip, bind_host_obj.host.port, bind_host_obj.remote_user.username, bind_host_obj.remote_user.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.84.66', port=6379) # 传一个命令列表给redis user_record = [user_obj.id, bind_host_obj.id, 'login', time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())] r = redis.Redis(connection_pool=pool) # 用用户名做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 = create_table.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+'_user_record' 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 = create_table.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(create_table.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)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#-*-coding:utf-8 -*- # Author: D.Gray import yaml try: from yaml import CLoader as Loader, CDumper as Dumper except ImportError: from yaml import Loader, Dumper def print_err(msg, quit=False): ''' :param msg: :param quit: :return: ''' output = "\033[31;1mError: %s\033[0m" % msg if quit: exit(output) else: print(output) def yaml_parser(yml_filename): ''' yaml方法load yaml file and return :param yml_filename: :return: ''' try: yaml_file = open(yml_filename, 'r') data = yaml.load(yaml_file) return data except Exception as e: print_err(e)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#-*-coding:utf-8 -*- # Author: D.Gray from database import create_table from pymysql.err import IntegrityError from conf import setting from modules.utils import print_err, yaml_parser from modules.db_conn import engine, session from modules import ssh_login from modules import common_filters import codecs,os def syncdb(argvs): ''' 创建表结构方法 :param argvs: :return: ''' print("Syncing DB....") engine = create_table.create_engine(setting.CONN, echo=True) create_table.Base.metadata.create_all(engine) # 创建所有表结构 def create_hosts(argvs): ''' create 主机 :param argvs: :return: ''' if '-f' in argvs: # 指定一个文件名否则报错 hosts_file = argvs[argvs.index("-f") +1] host_path = os.path.join(setting.BASE_DESC,hosts_file) #print('hosts_path:',host_path) else: print_err("invalid usage, should be:\ncreate_hosts -f <the new hosts file>", quit=True) source = yaml_parser(host_path) # 传文件回来 if source: # 循环字典 print(source) for key, val in source.items(): print(key, val) obj = create_table.Host(host_name=key, IP=val.get('ip'), port=val.get('port') or 22) # 添加到表 try: session.add(obj) except IntegrityError as e: print('主机名和主机IP是唯一值已在数据库创建:',e) else: session.commit() def create_remoteusers(argvs): ''' create 远程用户数据 :param argvs: :return: ''' if '-f' in argvs: remoteusers_file = argvs[argvs.index("-f") +1] host_path = os.path.join(setting.BASE_DESC, remoteusers_file) else: print_err("invalid usage, should be:\ncreate_remoteusers -f <the new remoteusers file>", quit=True) source = yaml_parser(host_path) if source: for key, val in source.items(): print(key, val) obj = create_table.RemUser(username=val.get('username'), auth_type=val.get('auth_type'), password=val.get('password')) session.add(obj) session.commit() def create_users(argvs): ''' create 堡垒机用户数据 create little_finger access user :param argvs: :return: ''' if '-f' in argvs: user_file = argvs[argvs.index("-f") +1 ] host_path = os.path.join(setting.BASE_DESC, user_file) else: print_err("invalid usage, should be:\ncreateusers -f <the new users file>",quit=True) source = yaml_parser(host_path) if source: for key, val in source.items(): print(key, val) obj = create_table.UserProfile(user_name=key, password=val.get('password')) if val.get('groups'): groups = session.query(create_table.Group).\ filter(create_table.Group.group_name.in_(val.get('groups'))).all() if not groups: print_err("none of [%s] exist in group table." % val.get('groups'), quit=True) obj.groups = groups if val.get('bind_hosts'): bind_hosts = common_filters.bind_hosts_filter(val) obj.bind_hosts = bind_hosts #print(obj) session.add(obj) session.commit() def create_groups(argvs): ''' create 组数据 create groups :param argvs: :return: ''' if '-f' in argvs: group_file = argvs[argvs.index("-f") + 1] host_path = os.path.join(setting.BASE_DESC, group_file) else: print_err("invalid usage, should be:\ncreategroups -f <the new groups file>", quit=True) source = yaml_parser(host_path) if source: for key, val in source.items(): print(key, val) obj = create_table.Group(group_name=key) if val.get('bind_hosts'): bind_hosts = common_filters.bind_hosts_filter(val) obj.bind_hosts = bind_hosts if val.get('user_profiles'): user_profiles = common_filters.user_profiles_filter(val) obj.user_profiles = user_profiles session.add(obj) session.commit() def create_bindhosts(argvs): ''' create IP和远程用户关联数据 create bind hosts :param argvs: :return: ''' if '-f' in argvs: bindhosts_file = argvs[argvs.index("-f") + 1] host_path = os.path.join(setting.BASE_DESC, bindhosts_file) else: print_err("invalid usage, should be:\ncreate_hosts -f <the new bindhosts file>",quit=True) source = yaml_parser(host_path) if source: for key, val in source.items(): print(key, val) # 获取到了主机 host_obj = session.query(create_table.Host).\ filter(create_table.Host.host_name == val.get('host_name')).first() # 取hostname assert host_obj # 断言,必须存在 for item in val['remote_users']: # 判断 print(item) assert item.get('auth_type') if item.get('auth_type') == 'ssh-password': # 判断认证password remoteuser_obj = session.query(create_table.RemUser).filter( create_table.RemUser.username == item.get('username'), create_table.RemUser.password == item.get('password') ).first() else: # 获取远程用户 remoteuser_obj = session.query(create_table.RemUser).filter( create_table.RemUser.username == item.get('username'), create_table.RemUser.auth_type == item.get('auth_type'), ).first() if not remoteuser_obj: # 没取到,程序退出 print_err("RemoteUser obj %s does not exist." % item, quit=True) bindhost_obj = create_table.BindHost(host_id=host_obj.host_id, remuser_id=remoteuser_obj.id) session.add(bindhost_obj) # 获取到关系后添加session # for groups this host binds to if source[key].get('groups'): # 获取组 group_objs = session.query(create_table.Group).filter(create_table.Group.group_name.in_ (source[key].get('groups'))).all() assert group_objs print('groups:', group_objs) bindhost_obj.host_groups = group_objs # for user_profiles this host binds to if source[key].get('user_profiles'): # 判断是否直接属于哪一台机器 userprofile_objs = session.query(create_table.UserProfile).\ filter(create_table.UserProfile.user_name.in_( source[key].get('user_profiles') )).all() assert userprofile_objs print("userprofiles:", userprofile_objs) bindhost_obj.user_profiles = userprofile_objs # print(bindhost_obj) session.commit() def auth(): ''' 用户验证 do the user login authentication :return: ''' count = 0 while count < 3: username = input("\033[32;1mUsername>>>:\033[0m").strip() if len(username) == 0: continue password = input("\033[32;1mPassword>>>:\033[0m").strip() if len(password) == 0: continue user_obj = session.query(create_table.UserProfile).filter(create_table.UserProfile.user_name == username, create_table.UserProfile.password == password).first() if user_obj: return user_obj else: print("wrong username or password, you have %s more chances." % (3-count-1)) count += 1 else: print_err("too many attempts.") def welcome_msg(user): ''' :param user: 接收start_session函数的user :return: ''' WELCOME_MSG = '''\033[32;1m ------------- Welcome [%s] login TinyServer ------------- \033[0m''' % user.user_name print(WELCOME_MSG) def start_session(argvs): ''' 开始远程登陆函数 :param argvs: :return: ''' print('going to start sesssion ') user = auth() #调用auth认证函数 来判断输入的堡垒机用户是否存在 if user: welcome_msg(user) # print(user.bind_hosts) # print(user.host_groups) exit_flag = False while not exit_flag: if user.profile_bind: # 显示未分组的机器 print('\033[32;1mz.\t z查看未分组主机列表/任意键查看已分组主机列表 (%s)\033[0m' % len(user.profile_bind)) for index, group in enumerate(user.profile_group): print('\033[32;1m%s.\t%s (%s)\033[0m' % (index, group.group_name, len(group.group_bind))) # 用户输入 choice = input("[%s]:" % user.user_name).strip() if len(choice) == 0: continue # 如果是z 打印未分组机器 if choice == 'z': print("------ Group: 未分组主机 ------") for index, bind_host in enumerate(user.profile_bind): print(" %s.\t%s@%s(%s)" % (index, bind_host.bind_remusers.username, bind_host.bind_hosts.host_name, bind_host.bind_hosts.IP, )) print("----------- END -----------") elif choice.isdigit(): # 打印分组的机器 choice = int(choice) if choice < len(user.profile_group): print("------ Group: %s ------" % user.profile_group[choice].group_name) for index, bind_host in enumerate(user.profile_group[choice].group_bind): print(" %s.\t%s@%s(%s)" % (index, bind_host.bind_remusers.username, bind_host.bind_hosts.host_name, bind_host.bind_hosts.IP, )) print("----------- END -----------") # host selection 选择机器去登陆 while not exit_flag: user_option = input("[(b)back, (q)quit, select host to login]:").strip() if len(user_option) == 0: continue if user_option == 'b': break if user_option == 'q': exit_flag = True if user_option.isdigit(): user_option = int(user_option) if user_option < len(user.host_groups[choice].bind_hosts): print('host:', user.host_groups[choice].bind_hosts[user_option]) # print('audit log:', user.host_groups[choice].bind_hosts[user_option].audit_logs) ssh_login.ssh_login(user, # 传用户,用户组,连上对应的 user.host_groups[choice].bind_hosts[user_option], session, log_recording) else: print("no this option..") def log_recording(user_obj, bind_host_obj, logs): ''' flush user operations on remote host into DB :param user_obj: :param bind_host_obj: :param logs: list format [logItem1,logItem2,...] :return: ''' # print("\033[41;1m--logs:\033[0m", logs) session.add_all(logs) session.commit() def user_record_cmd(argvs): ''' 查看操作记录方法 :param argvs: :return: ''' print('going to start view record') user = auth() # 默认root可以查所有人的记录 if user.user_name == 'root': print('welcome 【%s】 ' % user.user_name) exit_flag = False # 用户对象 user_obj = session.query(create_table.UserProfile).filter().all() # 循环查看堡垒机用户操作 while not exit_flag: for user_profile_list in user_obj: # 打印堡垒机用户,根据堡垒机用户ID选择其管辖的机器并打印日志 print("%s.\t%s" % (user_profile_list.id, user_profile_list.user_name)) choice = input("[%s]:" % user.user_name).strip() for user_profile_list in user_obj: if str(choice) == str(user_profile_list.id): if user_profile_list.profile_bind: # 显示未分组的机器 print('\033[32;1mz.\tungroupped hosts (%s)\033[0m' % len(user_profile_list.profile_bind)) else: print(' no binding groups ') for index, group in enumerate(user_profile_list.profile_group): print('\033[32;1m%s.\t%s (%s)\033[0m' % (index, group.group_name, len(group.group_bind))) choice = input("[%s]:" % user.user_name).strip() if choice.isdigit(): # 打印分组的机器 choice = int(choice) if choice < len(user_profile_list.profile_group): print("------ Group: %s ------" % user_profile_list.profile_group[choice].group_name) for index, bind_host in enumerate(user_profile_list.profile_group[choice].group_bind): print(" %s.\t%s@%s(%s)" % (index, bind_host.remote_user.user_name, bind_host.host.host_name, bind_host.host.IP, )) print("----------- END -----------") # host selection 选择机器去查看操作信息 while not exit_flag: user_option = input("[(b)back, (q)quit, select host to login]:").strip() if len(user_option) == 0: continue if user_option == 'b': break if user_option == 'q': exit_flag = True if user_option.isdigit(): user_option = int(user_option) if user_option < len(user_profile_list.profile_group[choice].bind_hosts): # print('host:', user_profile_list.host_groups[choice].bind_hosts[user_option]) data = \ session.query(create_table.AuditLog).filter( create_table.AuditLog.user_id == user_profile_list.id, create_table.AuditLog.bind_host_id == user_profile_list.profile_group[choice]. group_bind[user_option].id).all() if data: for index, i in enumerate(data): # redis 写入value的时候带有了\t \n 等需要转义 # 第一个注释从数据库里读注释的这种不能转移\t, # 第二个和现行的俩种中文转义有些问题 # print(i.user_id, i.bind_host_id, i.action_type, i.cmd, i.date) # print(i.user_id, i.bind_host_id, i.action_type, # codecs.getdecoder("unicode_escape")(i.cmd)[0], i.date) # print(i.user_id, i.bind_host_id, i.action_type, # i.cmd.encode().decode('unicode-escape'), i.date) print(index, i.date, i.cmd.encode().decode('unicode-escape')) else: print('no record in host:', user_profile_list.profile_group[choice]. group_bind[user_option]) # 其他人只能查自己的操作记录 else: exit_flag = False while not exit_flag: if user.profile_bind: # 显示未分组的机器 print('\033[32;1mz.\tungroupped hosts (%s)\033[0m' % len(user.profile_bind)) for index, group in enumerate(user.profile_group): print('\033[32;1m%s.\t%s (%s)\033[0m' % (index, group.group_name, len(group.group_bind))) choice1 = input("[%s]:" % user.user_name).strip() # 查询选项 if choice1 == 'z': print("------ Group: ungroupped hosts ------") for index, bind_host in enumerate(user.profile_bind): print(" %s.\t%s@%s(%s)" % (index, bind_host.remote_user.user_name, bind_host.host.host_name, bind_host.host.IP, )) print("----------- END -----------") elif choice1.isdigit(): # 打印分组的机器 choice = int(choice1) if choice < len(user.host_groups): print("------ Group: %s ------" % user.profile_group[choice].name) for index, bind_host in enumerate(user.profile_group[choice].group_bind): print(" %s.\t%s@%s(%s)" % (index, bind_host.remote_user.user_name, bind_host.host.host_name, bind_host.host.IP, )) print("----------- END -----------") # host selection 选择机器去查看操作信息 while not exit_flag: user_option = input("[(b)back, (q)quit, select host to view record]:").strip() if len(user_option) == 0: continue if user_option == 'b': break if user_option == 'q': exit_flag = True if user_option.isdigit(): user_option = int(user_option) if user_option < len(user.profile_group[choice].group_bind): data = session.query(create_table.AuditLog)\ .filter(create_table.AuditLog.user_id == user.id, create_table.AuditLog.bind_host_id == user.profile_group[choice]. group_bind[user_option].id).all() # print(user.host_groups[choice].bind_hosts[user_option].id) if data: for index, i in enumerate(data): print(index, i.date, i.cmd.encode().decode('unicode-escape')) else: print('no record in host:', user.profile_group[choice].group_bind[user_option]) else: print("no this option..")
share目录
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
bind1: host_name: server1 remote_users: - user0: username: root auth_type: ssh-password password: 123456 groups: - bj_group user_profiles: - sean bind2: host_name: server2 remote_users: - user0: username: root auth_type: ssh-password password: 123456 groups: - bj_group - sh_group user_profiles: - sean - jack bind3: host_name: server3 remote_users: - user0: username: root auth_type: ssh-password password: 123456 groups: - bj_group - sh_group user_profiles: - sean - jack bind4: host_name: server2 remote_users: - user2: username: colin auth_type: ssh-password password: 123@123 groups: - web_servers user_profiles: - root bind5: host_name: server3 remote_users: - user3: username: web auth_type: ssh-password password: 12345678 - user1: username: mysql auth_type: ssh-password password: 12345678 groups: - web_servers - db_servers user_profiles: - root
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
bj_group: user_profiles: - sean sh_group: user_profiles: - jack db_servers: user_profiles: - root web_servers: user_profiles: - root
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
server1: ip: 192.168.111.128 port: 22 server2: ip: 192.168.111.129 port: 22 server3: ip: 192.168.111.130 port: 22
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
user0: auth_type: ssh-password username: root password: 123456 user1: auth_type: ssh-password username: mysql password: 12345678 user2: auth_type: ssh-password username: colin password: 123@123 user3: auth_type: ssh-password username: web password: 12345678 user4: auth_type: ssh-key username: root
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
root: password: 123@456 sean: password: 123456 jack: password: 123456
六、测试路程图