堡垒机系统
需求:
所有的用户操作日志要保留在数据库中
每个用户登录堡垒机后,只需要选择具体要访问的设置,就连接上了,不需要再输入目标机器的访问密码
允许用户对不同的目标设备有不同的访问权限,例:
对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,sys 3 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_line 8 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,sys 2 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 "<user=%s,host=%s,action=%s,cmd=%s,date=%s>" %( 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 break 104 chan.send(d) 105 except EOFError: 106 # user hit ^Z or F6 107 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 paramiko 11 try: 12 import interactive 13 except ImportError: 14 from . import interactive 15 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 connection 19 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 pass 53 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" % msg 12 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 data 23 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 <the new hosts file>",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 <the new groups file>",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 <the new remoteusers file>",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 <the new userprofiles>",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 <the new bindhosts file>",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_obj 105 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 to 123 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_objs 126 print('groups:', group_objs) 127 bindhost_obj.host_groups = group_objs 128 #for user_profiles this host binds to 129 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_objs 134 print("userprofiles:",userprofile_objs) 135 bindhost_obj.user_profiles = userprofile_objs 136 #print(bindhost_obj) 137 session.commit() 138 139 def auth(): 140 """user认证""" 141 count = 0 142 while count<3: 143 username = input("Username:").strip() 144 if len(username)==0: continue 145 password = input("Password:").strip() 146 if len(password) ==0:continue 147 user_obj = session.query(models.UserProfile).filter(models.UserProfile.username==username, 148 models.UserProfile.password==password).first() 149 if user_obj: 150 return user_obj 151 else: 152 print("wrong username or password,you have %s ore chances"%(3-count-1)) 153 count+=1 154 else: 155 print_err("too many attempts") 156 def welcome(user): 157 WELCOME="""\033[32;1m 158 ------------- Welcome [%s] Fort Machine ------------- 159 \033[0m"""%user.username 160 print(WELCOME) 161 162 def log_recording(user_obj,bind_host_obj,logs): 163 ''' 164 flush user operations on remote host into DB 165 :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 = False 183 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:continue 190 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:continue 212 if user_option=="b":break 213 if user_option =="q": exit_flag=True 214 if user_option.isdigit(): 215 user_option=int(user_option) 216 if user_option <len(user.host_groups[choice].bindhosts): 217 print('host:', user.host_groups[choice].bindhosts[user_option]) 218 print('audit log:', user.host_groups[choice].bindhosts[user_option].audit_logs) 219 ssh_login.ssh_login(user, 220 user.host_groups[choice].bindhosts[user_option], 221 session, 222 log_recording) 223 else: 224 print("no this 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: alex 10 auth_type: ssh-passwd 11 password: alex3714 12 groups: 13 - beijing_group 14 user_profiles: 15 - alex 16 17 bind2: 18 hostname: centos-mysql 19 remote_users: 20 - user1: 21 username: alex 22 auth_type: ssh-passwd 23 password: alex3714 24 groups: 25 - beijing_group 26 - shanghai_group 27 28 user_profiles: 29 - rain
beijing_group: # bind_hosts: # - h1 # - h2 user_profiles: - alex shanghai_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!34321df 10 11 user2: 12 auth_type: ssh-key 13 username: root 14 #password: abc!23 15 16 user3: 17 auth_type: ssh-passwd 18 username: alex 19 password: alex3714
1 alex: 2 password: alex123 3 groups: 4 - beijing_group 5 6 #bind_hosts: 7 # - h1 8 # - h2 9 # - h3 10 11 jack: 12 password: jack123 13 groups: 14 - shanghai_group 15 16 rain: 17 password: rain123 18 #bind_hosts: 19 # - h1 20 # - h3