需求:
所有的用户操作日志要保留在数据库中 每个用户登录堡垒机后,只需要选择具体要访问的设置,就连接上了,不需要再输入目标机器的访问密码 允许用户对不同的目标设备有不同的访问权限,例: 对10.0.2.34 有mysql 用户的权限 对192.168.3.22 有root用户的权限 对172.33.24.55 没任何权限 分组管理,即可以对设置进行分组,允许用户访问某组机器,但对组里的不同机器依然有不同的访问权限 实现的需求: 针对不同的用户使用有权限的账户进入到指定的服务器中,进行操作,并记录下操作的命令 首先创建表数据 python bin\start.py syncdb 然后用yaml模块写入数据 例如:python bin\start.py create_hosts -f share\example\new_hosts.yml 数据导入完毕就开始连接远程系统 python bin\start.py start_session 输入堡垒机的用户名和密码:(alex,alex123) 显示出分组信息 选择需要进入的服务器信息(不用输入服务器的账号密码) 进入系统测试:在ubuntu16.04下 安装有mysql5.7.21 使用python3.5 远程系统centos6.5目录结构
数据库数据构建
流程图
代码:
1 #_*_coding:utf-8_*_2 import os,sys3 4 BASE_DIR=os.path.dirname(os.path.dirname(os.path.abspath(__file__)))5 sys.path.append(BASE_DIR)6 7 from modules.actions import excute_from_command_line8 if __name__=="__main__":9 excute_from_command_line(sys.argv)
1 #_*_coding:utf-8_*_ 2 from modules import views 3 4 actions = { 5 "syncdb": views.syncdb, 6 "create_hosts":views.create_hosts, 7 "create_groups":views.create_groups, 8 "create_remoteusers":views.create_remoteusers, 9 "create_userprofiles":views.create_userprofiles,10 "create_bindhosts":views.create_bindhosts,11 "start_session":views.start_session,12 }
1 import os,sys2 3 BASE_DIR =os.path.dirname(os.path.dirname(os.path.abspath(__file__)))4 5 6 ConnParams="mysql+pymysql://root:alex1234@192.168.189.129/fortdb?charset=utf8"
1 from sqlalchemy import Integer, ForeignKey, String, Column,Table,UniqueConstraint,DateTime 2 from sqlalchemy.ext.declarative import declarative_base 3 from sqlalchemy.orm import relationship 4 from sqlalchemy_utils import ChoiceType,PasswordType 5 6 7 8 9 Base=declarative_base() #创建数据表结构 10 11 userprofile_m2m_bindhost = Table("userprofile_m2m_bindhost",Base.metadata, 12 Column("userprofile_id",Integer,ForeignKey("user_profile.id")), 13 Column("bindhost_id",Integer,ForeignKey("bind_host.id")), 14 ) 15 16 hostgroup_m2m_bindhost = Table("hostgroup_m2m_bindhost",Base.metadata, 17 Column("hostgroup_id",Integer,ForeignKey("host_group.id")), 18 Column("bindhost_id",Integer,ForeignKey("bind_host.id")), 19 ) 20 21 userprofile_m2m_hostgroup = Table("userprofile_m2m_hostgroup",Base.metadata, 22 Column("userprofile_id",Integer,ForeignKey("user_profile.id")), 23 Column("hostgroup_id",Integer,ForeignKey("host_group.id")), 24 ) 25 26 class Host(Base):#主机 27 __tablename__="host" 28 id =Column(Integer,primary_key=True) 29 hostname=Column(String(64),unique=True) 30 ip = Column(String(64),unique=True) 31 port = Column(Integer,default=22) 32 33 34 def __repr__(self): 35 return self.hostname 36 class HostGroup(Base):#主机组 37 __tablename__="host_group" 38 id =Column(Integer,primary_key=True) 39 groupname=Column(String(64),unique=True) 40 41 bindhosts=relationship("BindHost",secondary="hostgroup_m2m_bindhost",backref="host_groups") 42 43 def __repr__(self): 44 return self.groupname 45 class RemoteUser(Base):#系统用户 46 __tablename__ = "remote_user" 47 __tableargs__ = UniqueConstraint("username","password","auth_type",name="user_passwd_uc")#联合唯一 48 AuthTypes = [ 49 ('ssh-passwd', 'SSH/Password'), 50 ('ssh-key', 'SSH/KEY'), 51 ] 52 id = Column(Integer, primary_key=True) 53 username = Column(String(64)) 54 password = Column(String(128)) 55 auth_type=Column(ChoiceType(AuthTypes)) 56 57 58 def __repr__(self): 59 return self.username 60 class UserProfile(Base):#堡垒机用户 61 __tablename__ = "user_profile" 62 id = Column(Integer, primary_key=True) 63 username = Column(String(64),unique=True) 64 password = Column(String(128)) 65 bind_hosts = relationship("BindHost",secondary="userprofile_m2m_bindhost",backref="user_profiles") 66 host_groups = relationship("HostGroup",secondary="userprofile_m2m_hostgroup",backref="user_profiles") 67 audit_logs = relationship('AuditLog') 68 69 def __repr__(self): 70 return self.username 71 class BindHost(Base): 72 """ 73 192.168.1.0 web 74 192.168.1.1 mysql 75 """ 76 __tablename__ = "bind_host" 77 __tableargs__=UniqueConstraint("host_id","remoteuser_id",name="host_remoteuser_uc") 78 id = Column(Integer, primary_key=True) 79 host_id = Column(Integer,ForeignKey("host.id")) 80 # group_id = Column(Integer,ForeignKey("host_group.id")) 81 remoteuser_id = Column(Integer,ForeignKey("remote_user.id")) 82 host=relationship("Host",backref="bind_hosts") 83 # group=relationship("HostGroup",backref="bind_hosts") 84 remoteuser=relationship("RemoteUser",backref="bind_hosts") 85 audit_logs = relationship('AuditLog') 86 87 def __repr__(self): 88 return "<%s --- %s>"%(self.host.ip,self.remoteuser.username) 89 class AuditLog(Base): 90 __tablename__ = 'audit_log' 91 id = Column(Integer,primary_key=True) 92 user_id = Column(Integer,ForeignKey('user_profile.id')) 93 bind_host_id = Column(Integer,ForeignKey('bind_host.id')) 94 action_choices= [ 95 ('cmd','CMD'), 96 ('login','Login'), 97 ('logout','Logout'), 98 ] 99 action_type = Column(ChoiceType(action_choices))100 cmd = Column(String(255))101 date = Column(DateTime)102 user_profile = relationship("UserProfile")103 bind_host = relationship("BindHost")104 105 # def __repr__(self):106 # return "" %(107 # self.user_profile.username,108 # self.bind_host.host.hostname,109 # self.action_type,110 # self.date)
1 #_*_coding:utf-8_*_ 2 3 4 from conf import settings 5 from conf import action_registers 6 from modules import utils 7 8 9 def help_msg():10 print("\033[31;1mAvailable commands:\033[0m")11 for key in action_registers.actions:12 print("\t",key)13 14 15 def excute_from_command_line(argvs):16 if len(argvs) <2:17 help_msg()18 exit()19 if argvs[1] not in action_registers.actions:20 utils.print_err("Command [%s] does not exist!" % argvs[1], quit=True)21 action_registers.actions[argvs[1]](argvs[1:])
1 #_*_coding:utf-8_*_ 2 3 from sqlalchemy import create_engine,Table 4 from sqlalchemy.orm import sessionmaker 5 6 from conf import settings 7 8 9 engine = create_engine(settings.ConnParams)10 11 12 Session = sessionmaker(bind=engine) #创建与数据库的会话session class ,注意,这里返回给session的是个class,不是实例13 session = Session()
1 import socket 2 import sys 3 from paramiko.py3compat import u 4 from models import models 5 import datetime 6 7 # windows does not have termios... 8 try: 9 import termios 10 import tty 11 has_termios = True 12 except ImportError: 13 has_termios = False 14 15 16 def interactive_shell(chan,user_obj,bind_host_obj,cmd_caches,log_recording): 17 if has_termios: 18 posix_shell(chan,user_obj,bind_host_obj,cmd_caches,log_recording) 19 else: 20 windows_shell(chan) 21 22 23 def posix_shell(chan,user_obj,bind_host_obj,cmd_caches,log_recording): 24 import select 25 26 oldtty = termios.tcgetattr(sys.stdin) 27 try: 28 tty.setraw(sys.stdin.fileno()) 29 tty.setcbreak(sys.stdin.fileno()) 30 chan.settimeout(0.0) 31 cmd = '' 32 33 tab_key = False 34 while True: 35 r, w, e = select.select([chan, sys.stdin], [], []) 36 if chan in r: 37 try: 38 x = u(chan.recv(1024)) 39 if tab_key: 40 if x not in ('\x07' , '\r\n'): 41 #print('tab:',x) 42 cmd += x 43 tab_key = False 44 if len(x) == 0: 45 sys.stdout.write('\r\n*** EOF\r\n') 46 break 47 sys.stdout.write(x) 48 sys.stdout.flush() 49 except socket.timeout: 50 pass 51 if sys.stdin in r: 52 x = sys.stdin.read(1) 53 if '\r' != x: 54 cmd +=x 55 else: 56 57 print('cmd->:',cmd) 58 log_item = models.AuditLog(user_id=user_obj.id, 59 bind_host_id=bind_host_obj.id, 60 action_type='cmd', 61 cmd=cmd , 62 date=datetime.datetime.now() 63 ) 64 cmd_caches.append(log_item) 65 cmd = '' 66 67 if len(cmd_caches)>=10: 68 log_recording(user_obj,bind_host_obj,cmd_caches) 69 cmd_caches = [] 70 if '\t' == x: 71 tab_key = True 72 if len(x) == 0: 73 break 74 chan.send(x) 75 76 finally: 77 termios.tcsetattr(sys.stdin, termios.TCSADRAIN, oldtty) 78 79 80 # thanks to Mike Looijmans for this code 81 def windows_shell(chan): 82 import threading 83 84 sys.stdout.write("Line-buffered terminal emulation. Press F6 or ^Z to send EOF.\r\n\r\n") 85 86 def writeall(sock): 87 while True: 88 data = sock.recv(256) 89 if not data: 90 sys.stdout.write('\r\n*** EOF ***\r\n\r\n') 91 sys.stdout.flush() 92 break 93 sys.stdout.write(data.decode()) 94 sys.stdout.flush() 95 96 writer = threading.Thread(target=writeall, args=(chan,)) 97 writer.start() 98 99 try:100 while True:101 d = sys.stdin.read(1)102 if not d:103 break104 chan.send(d)105 except EOFError:106 # user hit ^Z or F6107 pass
1 #_*_coding:utf-8_*_ 2 3 4 5 import sys 6 import traceback 7 from models import models 8 import datetime 9 10 import paramiko11 try:12 import interactive13 except ImportError:14 from . import interactive15 16 17 def ssh_login(user_obj,bind_host_obj,mysql_engine,log_recording):18 # now, connect and use paramiko Client to negotiate SSH2 across the connection19 try:20 client = paramiko.SSHClient()21 client.load_system_host_keys()22 client.set_missing_host_key_policy(paramiko.WarningPolicy())23 print('*** Connecting...')24 #client.connect(hostname, port, username, password)25 print(bind_host_obj)26 client.connect(bind_host_obj.host.ip,27 bind_host_obj.host.port,28 bind_host_obj.remoteuser.username,29 bind_host_obj.remoteuser.password,30 timeout=30)31 32 cmd_caches = []33 chan = client.invoke_shell()34 print(repr(client.get_transport()))35 print('*** Here we go!\n')36 cmd_caches.append(models.AuditLog(user_id=user_obj.id,37 bind_host_id=bind_host_obj.id,38 action_type='login',39 date=datetime.datetime.now()40 ))41 log_recording(user_obj,bind_host_obj,cmd_caches)42 interactive.interactive_shell(chan,user_obj,bind_host_obj,cmd_caches,log_recording)43 chan.close()44 client.close()45 46 except Exception as e:47 print('*** Caught exception: %s: %s' % (e.__class__, e))48 traceback.print_exc()49 try:50 client.close()51 except:52 pass53 sys.exit(1)
1 #_*_coding:utf-8_*_ 2 3 from conf import settings 4 import yaml 5 try: 6 from yaml import CLoader as Loader, CDumper as Dumper 7 except ImportError: 8 from yaml import Loader, Dumper 9 10 def print_err(msg,quit=False):11 output = "\033[31;1mError: %s\033[0m" % msg12 if quit:13 exit(output)14 else:15 print(output)16 17 18 def yaml_parser(yml_filename):19 try:20 yaml_file = open(yml_filename,'r')21 data = yaml.load(yaml_file)22 return data23 except Exception as e:24 print_err(e)
1 #_*_coding:utf-8_*_ 2 3 from models import models 4 from modules.db_conn import engine,session 5 from modules.utils import print_err,yaml_parser 6 # from modules import common_filters 7 from modules import ssh_login 8 9 10 def syncdb(argvs): 11 print("Syncing DB....") 12 models.Base.metadata.create_all(engine) #创建所有表结构 13 14 def create_hosts(argvs): 15 """ 16 创建主机数据host 17 :param argvs: 18 :return: 19 """ 20 if "-f" in argvs: 21 host_file = argvs[argvs.index("-f")+1] 22 else: 23 print_err("invalid usage, should be:\ncreate_hosts -f",quit=True) 24 source =yaml_parser(host_file) 25 if source: 26 for key,val in source.items(): 27 print(key,val) 28 obj=models.Host(hostname=key,ip=val.get("ip"),port=val.get("port") or 22) 29 session.add(obj) 30 session.commit() 31 def create_groups(argvs): 32 """ 33 创建主机组hostgroup 34 :param argvs: 35 :return: 36 """ 37 if "-f" in argvs: 38 hostgroups_file=argvs[argvs.index("-f")+1] 39 else: 40 print_err("invalid usage, should be:\ncreate_groups -f ",quit=True) 41 source = yaml_parser(hostgroups_file) 42 if source: 43 for key,val in source.items(): 44 print(key,val) 45 obj = models.HostGroup(groupname=key) 46 session.add(obj) 47 session.commit() 48 def create_remoteusers(argvs): 49 """ 50 创建系统用户remote_user 51 :param argvs: 52 :return: 53 """ 54 if "-f" in argvs: 55 remoteusers_file = argvs[argvs.index("-f")+1] 56 else: 57 print_err("invalid usage, should be:\ncreate_remoteusers -f ",quit=True) 58 source =yaml_parser(remoteusers_file) 59 if source: 60 for key,val in source.items(): 61 print(key,val) 62 obj=models.RemoteUser(username=val.get("username"),password=val.get("password"),auth_type=val.get("auth_type")) 63 session.add(obj) 64 session.commit() 65 def create_userprofiles(argvs): 66 """ 67 创建堡垒机用户user_profile 68 :param argvs: 69 :return: 70 """ 71 if "-f" in argvs: 72 userprofile=argvs[argvs.index("-f")+1] 73 else: 74 print_err("invalid usage,should be:\n create_userprofiles -f ",quit=True) 75 source = yaml_parser(userprofile) 76 if source: 77 for key,val in source.items(): 78 print(key,val) 79 obj = models.UserProfile(username=key,password=val.get("password")) 80 session.add(obj) 81 if val.get("groups"): 82 groups= session.query(models.HostGroup).filter\ 83 (models.HostGroup.groupname.in_(val.get("groups"))).all() 84 print(groups) 85 if not groups: 86 print_err("none of [%s] exist in groups tables"%val.get("groups"),quit=True) 87 obj.host_groups = groups 88 session.commit() 89 def create_bindhosts(argvs): 90 ''' 91 create bind hosts 92 :param argvs: 93 :return: 94 ''' 95 if '-f' in argvs: 96 bindhosts_file = argvs[argvs.index("-f") +1 ] 97 else: 98 print_err("invalid usage, should be:\ncreate_hosts -f ",quit=True) 99 source = yaml_parser(bindhosts_file)100 if source:101 for key,val in source.items():102 #print(key,val)103 host_obj = session.query(models.Host).filter(models.Host.hostname==val.get('hostname')).first()104 assert host_obj105 for item in val['remote_users']:106 print(item )107 assert item.get('auth_type')108 if item.get('auth_type') == 'ssh-passwd':109 remoteuser_obj = session.query(models.RemoteUser).filter(110 models.RemoteUser.username==item.get('username'),111 models.RemoteUser.password==item.get('password')112 ).first()113 else:114 remoteuser_obj = session.query(models.RemoteUser).filter(115 models.RemoteUser.username==item.get('username'),116 models.RemoteUser.auth_type==item.get('auth_type'),117 ).first()118 if not remoteuser_obj:119 print_err("RemoteUser obj %s does not exist." % item,quit=True )120 bindhost_obj = models.BindHost(host_id=host_obj.id,remoteuser_id=remoteuser_obj.id)121 session.add(bindhost_obj)122 #for groups this host binds to123 if source[key].get('groups'):124 group_objs = session.query(models.HostGroup).filter(models.HostGroup.groupname.in_(source[key].get('groups') )).all()125 assert group_objs126 print('groups:', group_objs)127 bindhost_obj.host_groups = group_objs128 #for user_profiles this host binds to129 if source[key].get('user_profiles'):130 userprofile_objs = session.query(models.UserProfile).filter(models.UserProfile.username.in_(131 source[key].get('user_profiles')132 )).all()133 assert userprofile_objs134 print("userprofiles:",userprofile_objs)135 bindhost_obj.user_profiles = userprofile_objs136 #print(bindhost_obj)137 session.commit()138 139 def auth():140 """user认证"""141 count = 0142 while count<3:143 username = input("Username:").strip()144 if len(username)==0: continue145 password = input("Password:").strip()146 if len(password) ==0:continue147 user_obj = session.query(models.UserProfile).filter(models.UserProfile.username==username,148 models.UserProfile.password==password).first()149 if user_obj:150 return user_obj151 else:152 print("wrong username or password,you have %s ore chances"%(3-count-1))153 count+=1154 else:155 print_err("too many attempts")156 def welcome(user):157 WELCOME="""\033[32;1m158 ------------- Welcome [%s] Fort Machine -------------159 \033[0m"""%user.username160 print(WELCOME)161 162 def log_recording(user_obj,bind_host_obj,logs):163 '''164 flush user operations on remote host into DB165 :param user_obj:166 :param bind_host_obj:167 :param logs: list format [logItem1,logItem2,...]168 :return:169 '''170 print("\033[41;1m--logs:\033[0m",logs)171 172 session.add_all(logs)173 session.commit()174 175 def start_session(argvs):176 print("going to start session")177 user=auth()178 if user:179 welcome(user)180 print(user.bind_hosts)181 print(user.host_groups)182 exit_flag = False183 while not exit_flag:184 if user.bind_hosts:185 print("ungroups host(%s)"%len(user.bind_hosts))186 for index,groups in enumerate(user.host_groups):187 print("%s.\t%s(%s)"%(index,groups.groupname,len(groups.bindhosts)))188 choice = input("[%s]:" % user.username).strip()189 if len(choice) == 0:continue190 if choice == "z":191 print("---Groups:ungroup hosts----")192 for index,bind_host in enumerate(user.bind_hosts):193 print("%s.\t%s@%s(%s)"%(index,194 bind_host.remoteuser.username,195 bind_host.host.hostname,196 bind_host.host.ip))197 print("-----END-----")198 elif choice.isdigit():199 choice = int(choice)200 if choice < len(user.host_groups):201 print("---Group:%s---"%user.host_groups[choice].groupname)202 for index,bind_host in enumerate(user.host_groups[choice].bindhosts):203 print("%s.\t%s@%s(%s)"%(index,204 bind_host.remoteuser.username,205 bind_host.host.hostname,206 bind_host.host.ip))207 print("-----END-----")208 209 while not exit_flag:210 user_option = input("[(b)back,(q)quit,select to host login]:").strip()211 if len(user_option) ==0:continue212 if user_option=="b":break213 if user_option =="q": exit_flag=True214 if user_option.isdigit():215 user_option=int(user_option)216 if user_option
1 bind1: 2 hostname: ubuntu-test 3 remote_users: 4 - user1: 5 username: root 6 auth_type: ssh-key 7 #password: 123 8 - user2: 9 username: alex10 auth_type: ssh-passwd11 password: alex371412 groups:13 - beijing_group14 user_profiles:15 - alex16 17 bind2:18 hostname: centos-mysql19 remote_users:20 - user1:21 username: alex22 auth_type: ssh-passwd23 password: alex371424 groups:25 - beijing_group26 - shanghai_group27 28 user_profiles:29 - rain
beijing_group:# bind_hosts:# - h1# - h2 user_profiles: - alexshanghai_group: user_profiles: - jack - alex - rain
1 ubuntu-test: 2 ip: 192.168.3.22 3 port: 22 4 5 redhat: 6 ip: 172.33.24.55 7 port: 3000 8 9 centos-mysql:10 ip: 10.0.2.34
1 user0: 2 auth_type: ssh-passwd 3 username: root 4 password: abc!23 5 6 user1: 7 auth_type: ssh-passwd 8 username: root 9 password: alex!34321df10 11 user2:12 auth_type: ssh-key13 username: root14 #password: abc!2315 16 user3:17 auth_type: ssh-passwd18 username: alex19 password: alex3714
1 alex: 2 password: alex123 3 groups: 4 - beijing_group 5 6 #bind_hosts: 7 # - h1 8 # - h2 9 # - h310 11 jack:12 password: jack12313 groups:14 - shanghai_group15 16 rain:17 password: rain12318 #bind_hosts:19 # - h120 # - h3