认识堡垒机
拓展两个知识点:
1、traceback:出异常,会具体打印出哪一行
traceback.print_exc()
2、getpass模块获取用户名:
uson@ubuntu:~$ python3 Python 3.6.8 (default, Aug 20 2019, 17:12:48) [GCC 8.3.0] on linux Type "help", "copyright", "credits" or "license" for more information. >>> import getpass >>> getpass.getuser() 'uson'
3、Ubuntu配置用户的环境变量:
source:使当前shell读入路径为filepath的shell文件并依次执行文件中的所有语句,通常用于重新执行刚修改的初始化文件,使之立即生效,而不必注销并重新登录
命令行输入:mysql -uuson -pakaedu改成source .bashrc执行 # 每个用户目录下都有一个.bashrc文件 #(1)vim .bashrc 在最后新增一行命令行输入的东西: mysql -uuson -pakaedu 或者 python3 .../.../..../.py # 保存文件的绝对路径abspath # (2)执行.bashrc source .bashrc

4、字典的用法补充:
val.get(kek) or str 获取字典key, 获取不到,选择or ---->val为字典
val.get(key)
5、assert host_obj 断言,即要求host_obj必须存在,否则报错
当断言条件为假时,抛出异常AssertionError
6、本例用到yaml模块,安装pyyaml模块
7、本例数据库建造时用到枚举类型ChoiceType(),需要用到一个插件
from sqlalchemy_utils import ChoiceType
堡垒机环节:
堡垒机的作用:
1、不需要提供root密码给运维
2、记录用户操作记录(审计)
3、用户权限控制
只有堡垒机管理员知道root密码
记住,表结构设计好之前,不要写代码
配置文件一般只写配置(类似变量类的赋值语句),不写动作(func())
正文:
******下载paramiko(本质上就是封装了ssh)文件,我们用到demo.py文件和interactive.py文件,并修改
业务逻辑结构:

代码示例:
bin目录:

1 操作流程: 2 堡垒机用户及主机组之间的数据实例化,和关联: 3 1、入口create_groups,不添加关联数据(注释bind_hosts/user_profiles):已实例化主机组 new_groups.yml 4 add_groups,不添加关联数据(注释bind_hosts/user_profiles),添加主机组数据一条 add_groups.yml 5 2、create_users,关联检测hostgroups,不关联bind_hosts数据,已实例化堡垒机用户 new_user2.yml 6 已自动关联hostgroup,userprofile 7 3、检测:create_groups中的user_profiles关联是否有问题 8 取消注释user_profiles,新加一个主机组反关联检测user_profiles add_groups2.yml 9 10 以上均无问题后,继续 11 bindhost关联所有 12 4、create_hosts new_hosts.yml 13 5、create_remoteusers new_remoteusers.yml 14 15 6、BindHost>>>

1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 # Author:Uson 4 import os,sys 5 6 BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 7 print(BASE_DIR) 8 9 sys.path.append(BASE_DIR) 10 11 from modules.actions import excute_from_command_line 12 13 if __name__ == '__main__': 14 #执行脚本命令行参数判断 python fuck_run.py syncdb 15 #argv[0]是脚本名fuck_run.py,argv[1]是第一个命令行参数syncdb 16 excute_from_command_line(sys.argv) #sys.argv #传进来的是列表形式

1 #dict: many val = list[ {} ]
2 #Chinese is not supported
3
4 bind1:
5 hostname: server1
6 remote_users: #dict{[-,-]} #{ [ {user1:...}, {user2:...} ] }
7 # [{'user1': None, 'auth_type': 'ssh-key', 'username': 'root'},
8 # {'username': 'alex', 'password': 'alex3714', 'user2': None, 'auth_type': 'ssh-password'}]
9 - user1: #list[{:,:,}] #- [ {user1:None, username:root, ...}, {}, ...]
10 username: root
11 auth_type: ssh-key
12 #password: 123
13 - user2: #[]
14 username: alex
15 auth_type: ssh-password
16 password: alex3714
17 # remote_users:
18 # - user1:
19 # - username: root
20 # - auth_type: ssh-key
21 # - #password: 123
22 # - user2:
23 # - username: alex
24 # - auth_type: ssh-password
25 # - password: alex3714
26 groups:
27 - log_devteam
28 user_profiles:
29 - alex
30
31 bind2:
32 hostname: server2
33 remote_users:
34 - user1:
35 username: alex
36 auth_type: ssh-password
37 password: alex3714
38 # remote_users: dict{ [user1:[ username:alex, auth_type:ssh-password, ... ], [], ...] }
39 # - user1:
40 # - username: alex
41 # - auth_type: ssh-password
42 # - password: alex3714
43 #item: {'user1': [{'username': 'alex'}, {'auth_type': 'ssh-password'}, {'password': 'alex3714'}]} all is :key
44 #key: {'user1': [{'username': 'alex'}, {'auth_type': 'ssh-password'}, {'password': 'alex3714'}]}
45 groups:
46 - web_devteam
47 - mysql_devteam
48
49 user_profiles:
50 - rain
conf目录:

1 #!/usr/bin/env python
2 # -*- coding:utf-8 -*-
3 # Author:Uson
4
5 from modules import views, views_add
6
7 '''
8 本测试:
9 (1)创建主机组,不创建binhost/userprofile
10 (2)创建用户,
11 '''
12
13 actions = {
14 'syncdb': views.syncdb, #连接数据库,创建数据表 #(第一步)
15 'create_hosts': views.create_hosts, # (第二步)
16 'create_remoteusers': views.create_remoteusers, # (第三步)
17
18 'create_users': views.create_users, #(第四步)
19 'create_groups': views.create_groups, #(第五步)
20
21 'create_bindhosts': views.create_bindhosts, #(第六步)
22
23 'start_session': views.start_session, #(第七步)
24
25 # 'stop': views.stop_server, #(第八步)暂未实现
26
27 'hand_modify': views.hand_modify, #暂未实现,只能创建不能修改 #一条一条手动输入修改,非文件提交
28
29 # 'audit': views.log_audit, #查看所有运维人员的记录,暂未实现
30
31 'add_groups': views_add.add_groups, #主机组添加数据,针对死循环,没有入口,不含关联数据
32 }

1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 # Author:Uson 4 import os,sys 5 6 # BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 7 8 ConnParams = "mysql+pymysql://uson:akaedu@192.168.1.6/uson_fuckdb?charset=utf8" #只写配置不写动作
models目录:

1 #!/usr/bin/env python
2 # -*- coding:utf-8 -*-
3 # Author:Uson
4 '''堡垒机用户userprofile与hostgroup多对多关联'''
5 from conf import settings
6 from sqlalchemy import create_engine
7 from sqlalchemy.ext.declarative import declarative_base
8 from sqlalchemy import Column, Integer, String, Date, Table, Enum
9 from sqlalchemy import ForeignKey, UniqueConstraint #联合唯一键
10 from sqlalchemy.orm import relationship
11
12 from sqlalchemy_utils import ChoiceType
13
14 engine = create_engine(settings.ConnParams)
15 Base = declarative_base()
16
17 # '''(2)删除:多对多关联:第三方表的建设'''
18 # Host_To_Remoteuser = Table(
19 # 'host_to_remoteuser', Base.metadata, #元数据,中介数据 == 实例化Metadata()后的metadata
20 # Column('host_id', Integer, ForeignKey('host.id')), #需要用,分隔
21 # Column('remoteuser_id', Integer, ForeignKey('remoteuser.id')), #需要用,分隔
22 # )
23
24 '''(3)多对多关联:堡垒机用户第三方表的建设'''
25 Userprofile_To_Bindhost = Table(
26 'userprofile_to_bindhost', Base.metadata,
27 Column('userprofile_id', Integer, ForeignKey('userprofile.id')),
28 Column('bindhost_id', Integer, ForeignKey('bindhost.id')),
29 )
30 '''(4)多对多关联:主机组第三方表的建设'''
31 Bindhost_To_Hostgroup = Table(
32 'bindhost_to_hostgroup', Base.metadata,
33 Column('hostgroup_id', Integer, ForeignKey('hostgroup.id')),
34 Column('bindhost_id', Integer, ForeignKey('bindhost.id')),
35 )
36 '''(5)多对多关联:主机组第三方表的建设,堡垒机用户userprofile与hostgroup多对多关联'''
37 Userprofile_To_Hostgroup = Table(
38 'userprofile_to_hostgroup', Base.metadata,
39 Column('hostgroup_id', Integer, ForeignKey('hostgroup.id')),
40 Column('userprofile_id', Integer, ForeignKey('userprofile.id')),
41 )
42
43 '''(2)新增一张表:存host,user, hostgroup'''
44 class BindHost(Base):
45 '''
46 host user host_group
47 192.168.1.1 web bj_group
48 192.168.1.1 log sh_group
49 '''
50 __tablename__ = 'bindhost'
51
52 '''联合唯一'''
53 __table_args__ = (UniqueConstraint('host_id', 'hostgroup_id', 'remoteuser_id'),) # 联合唯一键
54
55 id = Column(Integer, primary_key=True)
56 '''(2)不用第三张表的多对多方式:一条一个外键:多个外键多个关系'''
57 host_id = Column(Integer, ForeignKey('host.id'))
58 # hostgroup_id = Column(Integer, ForeignKey('hostgroup.id')) (4)
59 remoteuser_id = Column(Integer, ForeignKey('remoteuser.id'))
60
61 hosts = relationship("Host", backref = 'bind_hosts')
62 # hostgroups = relationship("HostGroup", backref = 'bind_hosts') (4)
63 remoteusers = relationship("RemoteUser", backref = 'bind_hosts')
64 def __repr__(self):
65 return "<%s - %s - %%s>" %(self.hosts.ip,
66 # self.hostgroups.name, (4)
67 self.remoteusers.username)
68
69 '''堡垒机表结构 www.processon.com'''
70 class Host(Base): #远程主机
71 __tablename__ = 'host'
72 id = Column(Integer, primary_key=True) #主键
73 hostname = Column(String(32), unique=True) #主机名(非ip)唯一
74 ip = Column(String(32), unique=True) #主机ip唯一
75 port = Column(Integer, default=22) #端口号默认22
76
77 # '''(2)删除:Host_To_Remoteuser'''
78 # remote_users = relationship('RemoteUser', secondary = Host_To_Remoteuser, backref = 'hosts')
79
80 def __repr__(self):
81 return self.hostname
82 class HostGroup(Base):#远程主机组
83 __tablename__ = 'hostgroup'
84 id = Column(Integer, primary_key=True) # 主键
85 name = Column(String(32), unique=True) #主机组名唯一
86
87 '''(4)建立关系'''
88 bindhosts = relationship("BindHost", seeondary = Bindhost_To_Hostgroup, backref = 'host_groups')
89
90 def __repr__(self):
91 return self.name
92 class RemoteUser(Base):#远程主机用户
93 __tablename__ = 'remoteuser'
94
95 '''用户名和密码多对多关系:要使用联合唯一'''
96 __table_args__ = (UniqueConstraint('auth_type', 'username', 'password'),)#联合唯一键
97
98 id = Column(Integer, primary_key=True) # 主键
99
100 # auth_type = Column(Enum(0,1))
101 '''认证类型:列表枚举型,经典案例,用到sqlalchemy_utils模块下的ChoiceType'''
102 AuthTypes = [
103 ('ssh-password', 'SSH/Password'), # 前者写入数据库,后者显示给用户
104 ('ssh-key', 'SSH/KEY'),
105 ]
106 auth_type = Column(ChoiceType(AuthTypes)) #从列表中选择类型
107
108 username = Column(String(32)) # 可以同用户名root,不同密码
109 password = Column(String(128)) # 可以同密码,不同用户名
110 def __repr__(self):
111 return self.username
112 class UserProfile(Base):#堡垒机用户
113 __tablename__ = 'userprofile'
114 id = Column(Integer, primary_key=True) # 主键
115 username = Column(String(32), unique=True) #堡垒机用户名唯一
116 password = Column(String(128))
117
118 '''(3)堡垒机用户第三张表建设'''
119 bindhosts = relationship("BindHost", secondary = Userprofile_To_Bindhost, backref = 'user_profiles')
120 '''(5)'''
121 hostgroups = relationship("HostGroup", secondary = Userprofile_To_Hostgroup, backref = 'user_profiles')
122 def __repr__(self):
123 return self.username
124 class Auditlog(Base): #堡垒机操作日志
125 __tablename__ = 'host'
126 id = Column(Integer, primary_key=True) # 主键
127 def __repr__(self):
128 return self.id
129
130 # Base.metadata.create_all(engine) # 创建表结构

1 #!/usr/bin/env python
2 # -*- coding:utf-8 -*-
3 # Author:Uson
4 '''此例表结构与堡垒机案例表结构有出入,灵活运用'''
5 from sqlalchemy.ext.declarative import declarative_base
6 from sqlalchemy import Column, Integer, String, Date, Table, Enum, DateTime
7 from sqlalchemy import ForeignKey, UniqueConstraint #联合唯一键
8 from sqlalchemy.orm import relationship
9 from sqlalchemy_utils import ChoiceType
10 Base = declarative_base()
11
12 Userprofile_To_Bindhost = Table(
13 'userprofile_to_bindhost', Base.metadata,
14 Column('userprofile_id', Integer, ForeignKey('userprofile.id')),
15 Column('bindhost_id', Integer, ForeignKey('bindhost.id')),
16 )
17
18 Bindhost_To_Hostgroup = Table(
19 'bindhost_to_hostgroup', Base.metadata,
20 Column('hostgroup_id', Integer, ForeignKey('hostgroup.id')),
21 Column('bindhost_id', Integer, ForeignKey('bindhost.id')),
22 )
23
24 Userprofile_To_Hostgroup = Table(
25 'userprofile_to_hostgroup', Base.metadata,
26 Column('hostgroup_id', Integer, ForeignKey('hostgroup.id')),
27 Column('userprofile_id', Integer, ForeignKey('userprofile.id')),
28 )
29
30 class BindHost(Base):
31 '''
32 host user host_group
33 192.168.1.1 web x
34 192.168.1.1 log x
35 '''
36 __tablename__ = 'bindhost'
37 __table_args__ = (UniqueConstraint('host_id', 'remoteuser_id', name='_host_remoteuser_uc'),) # 联合唯一键
38
39 id = Column(Integer, primary_key=True)
40 host_id = Column(Integer, ForeignKey('host.id'))
41 remoteuser_id = Column(Integer, ForeignKey('remoteuser.id'))
42
43 hosts = relationship("Host", backref = 'bind_hosts')
44 remoteusers = relationship("RemoteUser", backref = 'bind_hosts')
45
46 audit_logs = relationship('AuditLog')
47
48 # hostgroups = relationship("HostGroup", secondary = Bindhost_To_Hostgroup, backref = 'bind_hosts')
49 def __repr__(self): #循环ip地址,远程用户
50 return "<%s - %s - %s>" %(self.hosts.hostname,
51 self.remoteusers.username,
52 self.id)
53
54 class Host(Base):
55 __tablename__ = 'host'
56 id = Column(Integer, primary_key=True)
57 hostname = Column(String(32), unique=True)
58 ip = Column(String(32), unique=True)
59 port = Column(Integer, default=22)
60
61 def __repr__(self):
62 return self.hostname
63 class HostGroup(Base):
64 __tablename__ = 'hostgroup'
65 id = Column(Integer, primary_key=True)
66 name = Column(String(32), unique=True)
67
68 bindhosts = relationship("BindHost", secondary = Bindhost_To_Hostgroup, backref = 'host_groups')
69
70 def __repr__(self):
71 return self.name
72 class RemoteUser(Base):#远程主机用户
73 __tablename__ = 'remoteuser'
74
75 '''用户名和密码多对多关系:要使用联合唯一'''
76 __table_args__ = (UniqueConstraint('auth_type', 'username', 'password', name='_authtype_usernm_pwd_uc'),)
77
78 id = Column(Integer, primary_key=True)
79 AuthTypes = [
80 ('ssh-password', 'SSH/Password'), # 前者写入数据库,后者显示给用户
81 ('ssh-key', 'SSH/KEY'),
82 ]
83 auth_type = Column(ChoiceType(AuthTypes)) #从列表中选择类型 (需要实例化)
84
85 username = Column(String(32)) # 可以同用户名root,不同密码 (需要实例化)
86 password = Column(String(128)) # 可以同密码,不同用户名 (可以为NUll,根据类型选择实例化)
87 def __repr__(self):
88 return self.username
89
90 class UserProfile(Base):#堡垒机用户
91 __tablename__ = 'userprofile'
92 id = Column(Integer, primary_key=True) # 主键
93 username = Column(String(32), unique=True) #堡垒机用户名唯一
94 password = Column(String(128))
95
96 '''secondary='',写表名'''
97 '''secondary=,写开发者起的名字'''
98 bindhosts = relationship("BindHost", secondary = Userprofile_To_Bindhost, backref = 'user_profiles')
99 hostgroups = relationship("HostGroup", secondary = Userprofile_To_Hostgroup, backref = 'user_profiles')
100 def __repr__(self):
101 return self.username #[]
102 class AuditLog(Base): #堡垒机操作日志
103 __tablename__ = 'audit_log'
104 id = Column(Integer, primary_key=True)
105 user_id = Column(Integer, ForeignKey('userprofile.id')) #需要实例化
106 bind_host_id = Column(Integer, ForeignKey('bindhost.id')) #需要实例化
107
108 '''枚举类型'''
109 # action_choices = [
110 # (0, 'CMD'),
111 # (1, 'Login'),
112 # (2, 'Logout'),
113 # (3, 'GetFile'),
114 # (4, 'SendFile'),
115 # (5, 'Exception'),
116 # ]
117 action_choices2 = [
118 ('cmd', 'CMD'), #python3默认是Unicode,不需要加u
119 ('login', 'Login'),
120 ('logout', 'Logout'),
121 # (3,'GetFile'),
122 # (4,'SendFile'),
123 # (5,'Exception'),
124 ]
125 action_type = Column(ChoiceType(action_choices2)) #需要实例化
126
127 # action_type = Column(String(64))
128 cmd = Column(String(255)) #可选的,可为Null,类似密码密钥,如果不是cmd操作,login/logout:可以是NUll
129 date = Column(DateTime) #需要实例化
130
131 user_profile = relationship("UserProfile")
132 bind_host = relationship("BindHost")
modules目录:

1 #!/usr/bin/env python
2 # -*- coding:utf-8 -*-
3 # Author:Uson
4 from conf import action_registers
5 from modules import utils
6 def help_msg():
7 '''
8 print help msgs
9 :return:
10 '''
11 print("\033[31;1m参数个数输入不合法:\033[0m")
12 '''循环打印配置中的命令字典'''
13 print("\033[32;1m请从以下选项中选择操作。\033[0m")
14 for key in action_registers.actions:
15 print('\t', key)
16
17 def excute_from_command_line(argvs):
18 if len(argvs) < 2: #python fuck_run.py 列表长度
19 help_msg()
20 exit()
21 if argvs[1] not in action_registers.actions:
22 '''打印错误信息'''
23 utils.print_err("【%s】命令不存在"%argvs[1], quit = True)
24 # utils.print_err("Command [%s] does not exist!" % argvs[1], quit=True)
25 #命令存在的情况
26 # action_registers.actions[argvs[1]]() #可以不传参数
27 action_registers.actions[argvs[1]](argvs[1:]) #可以不传参数,但可以加个非固定参数(参数列表),增加函数的可拓展性

1 #!/usr/bin/env python
2 # -*- coding:utf-8 -*-
3 # Author:Uson
4 from models import models
5 from modules.db_conn import engine,session
6 from modules.utils import print_err
7
8 def bind_hosts_filter(vals):
9 print('**>',vals.get('bind_hosts') )
10
11 '''只能用于反查询'''
12 # bind_hosts2 = session.query(models.BindHost).filter(models.BindHost.hosts.hostname.in_(vals.get('bind_hosts') )).all()
13
14 #仅返回hostname,无法关联
15 # bind_hosts = session.query(models.Host).filter(models.Host.hostname.in_(vals.get('bind_hosts'))).all()
16
17 bind_hosts = session.query(models.BindHost).filter(models.Host.hostname.in_(vals.get('bind_hosts'))).all()
18 print("1:2对比:", bind_hosts)
19 # print("1:2对比:", bind_hosts)
20 if not bind_hosts:
21 # print_err("none of [%s] exist in bind_host table." % vals.get('bind_hosts'),quit=False)
22 print_err("未分组",quit=False)
23 return bind_hosts
24
25 def user_profiles_filter(vals):
26 user_profiles = session.query(models.UserProfile).filter(models.UserProfile.username.in_(vals.get('user_profiles'))
27 ).all()
28 if not user_profiles:
29 print_err("none of [%s] exist in user_profile table." % vals.get('user_profiles'),quit=True)
30 return user_profiles

1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 # Author:Uson 4 from sqlalchemy import create_engine 5 from conf import settings 6 7 from sqlalchemy.orm import sessionmaker 8 9 engine = create_engine(settings.ConnParams) 10 11 Session_class = sessionmaker(bind=engine)#创建与数据库的会话session class ,注意,这里返回给session的是个class,不是实例 12 session = Session_class()

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 models import models
24 import datetime
25
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
34
35 def interactive_shell(chan,user_obj,bind_host_obj,cmd_caches,log_recording):
36 if has_termios:
37 posix_shell(chan,user_obj,bind_host_obj,cmd_caches,log_recording)
38 else:
39 # windows_shell(chan)
40 windows_shell(chan, user_obj,bind_host_obj,cmd_caches,log_recording)
41
42
43 def posix_shell(chan,user_obj,bind_host_obj,cmd_caches,log_recording):
44 import select
45
46 oldtty = termios.tcgetattr(sys.stdin)
47 try:
48 tty.setraw(sys.stdin.fileno())
49 tty.setcbreak(sys.stdin.fileno())
50 chan.settimeout(0.0)
51 cmd = ''
52
53 tab_key = False
54 while True:
55 r, w, e = select.select([chan, sys.stdin], [], [])
56 if chan in r:
57 try:
58 x = u(chan.recv(1024))
59 if tab_key:
60 if x not in ('\x07' , '\r\n'): #tab自动补全功能
61 print('tab:',x)
62 cmd += x
63 tab_key = False
64 if len(x) == 0:
65 sys.stdout.write('\r\n*** EOF\r\n')
66 break
67 sys.stdout.write(x)
68 sys.stdout.flush()
69 except socket.timeout:
70 pass
71 if sys.stdin in r:
72 x = sys.stdin.read(1)
73 if '\r' != x:
74 cmd +=x
75 else:
76
77 print('cmd->:',cmd)
78
79 '''CMD记录实例化'''
80 log_item = models.AuditLog(user_id=user_obj.id,
81 bind_host_id=bind_host_obj.id,
82 action_type='cmd',
83 cmd=cmd , #ls
84 date=datetime.datetime.now()
85 )
86 cmd_caches.append(log_item)
87 cmd = ''
88
89 if len(cmd_caches)>=10:
90 # log_recording(cmd_caches) #实际有用的就是cmd_caches,提交到数据库
91 log_recording(user_obj,bind_host_obj,cmd_caches)
92 cmd_caches = []
93 if '\t' == x:
94 tab_key = True
95 if len(x) == 0:
96 break
97 chan.send(x)
98
99 finally:
100 termios.tcsetattr(sys.stdin, termios.TCSADRAIN, oldtty)
101
102
103 # thanks to Mike Looijmans for this code
104 # def windows_shell(chan):
105 # import threading
106 #
107 # sys.stdout.write("Line-buffered terminal emulation. Press F6 or ^Z to send EOF.\r\n\r\n")
108 #
109 # def writeall(sock):
110 # while True:
111 # data = sock.recv(256)
112 # if not data:
113 # sys.stdout.write('\r\n*** EOF ***\r\n\r\n')
114 # sys.stdout.flush()
115 # break
116 # sys.stdout.write(data.decode())
117 # sys.stdout.flush()
118 #
119 # writer = threading.Thread(target=writeall, args=(chan,))
120 # writer.start()
121 #
122 # try:
123 # while True:
124 # d = sys.stdin.read(1)
125 # if not d:
126 # break
127 # chan.send(d)
128 # except EOFError:
129 # # user hit ^Z or F6
130 # pass
131
132 def windows_shell(chan, user_obj,bind_host_obj,cmd_caches,log_recording):
133 import threading
134
135 sys.stdout.write("Line-buffered terminal emulation. Press F6 or ^Z to send EOF.\r\n\r\n")
136
137 def writeall(sock):
138 while True:
139 data = sock.recv(256) #我们不用记录输出结果
140 if not data:
141 sys.stdout.write('\r\n*** EOF ***\r\n\r\n')
142 sys.stdout.flush()
143 break
144 sys.stdout.write(data.decode()) #屏幕输出命令结果
145 sys.stdout.flush()
146
147 writer = threading.Thread(target=writeall, args=(chan, ))
148 writer.start()
149
150 '''Windows版审计,自己实现》》'''
151 cmd = ''
152 try:
153 while True:
154 d = sys.stdin.read(1)
155 # print("D:", d)
156 if not d:
157 break
158 cmd += d
159 # print("cmd:", cmd)
160 # if d == '' or d == '\r' or d == '\r\n' or d == '\n':
161 if d == '\n' or d == '\r\n': #windows回车显示是空格,但验证后,回车不等于'',而是'\n'
162 # print("cmd_cmd:", cmd)
163 '''CMD记录实例化'''
164 log_item = models.AuditLog(user_id=user_obj.id,
165 bind_host_id=bind_host_obj.id,
166 action_type='cmd',
167 cmd=cmd, # ls
168 date=datetime.datetime.now()
169 )
170 cmd_caches.append(log_item)
171 # print("cmd_caches:", cmd_caches, len(cmd_caches))
172 cmd = ''
173
174 if len(cmd_caches) >= 10 or cmd == 'exit' or cmd == '^Z':
175 # log_recording(cmd_caches) #实际有用的就是cmd_caches,提交到数据库
176 log_recording(user_obj, bind_host_obj, cmd_caches)
177 cmd_caches = []
178 if sys.stdout.write('*** EOF ***'):
179 log_recording(user_obj, bind_host_obj, cmd_caches)
180
181 chan.send(d)
182 except EOFError:
183 # user hit ^Z or F6
184
185 '''退出记录审计'''
186 log_recording(user_obj, bind_host_obj, cmd_caches)
187
188 pass

1 #!/usr/bin/env python
2 # -*- coding:utf-8 -*-
3 # Author:Uson
4
5 import base64
6 import getpass
7 import os
8 import socket
9 import sys
10 import traceback
11 from paramiko.py3compat import input
12 from models import models
13 import datetime
14
15 import paramiko
16 try:
17 import interactive
18 except ImportError:
19 from . import interactive #同级目录下用.
20
21 def ssh_login(user_obj,bind_host_obj,mysql_engine,log_recording):
22 # now, connect and use paramiko Client to negotiate SSH2 across the connection
23 try:
24 client = paramiko.SSHClient()
25 client.load_system_host_keys()
26 client.set_missing_host_key_policy(paramiko.WarningPolicy())
27 print('*** Connecting...')
28 #client.connect(hostname, port, username, password)
29
30 '''需要修改'''
31 client.connect(bind_host_obj.hosts.ip,
32 bind_host_obj.hosts.port,
33 bind_host_obj.remoteusers.username,
34 bind_host_obj.remoteusers.password,
35 timeout=30)
36
37 cmd_caches = []
38 chan = client.invoke_shell()
39 print(repr(client.get_transport()))
40 print('*** Here we go!\n')
41
42 '''日志模块:login记录实例化'''
43 '''interactive.py:CMD记录实例化'''
44 cmd_caches.append(models.AuditLog(user_id=user_obj.id, #外键实例化
45 bind_host_id=bind_host_obj.id, #外键实例化
46 action_type='login', #cmd是Null
47 date=datetime.datetime.now()
48 ))
49 log_recording(user_obj,bind_host_obj,cmd_caches)
50 interactive.interactive_shell(chan,user_obj,bind_host_obj,cmd_caches,log_recording)
51 chan.close()
52 client.close()
53
54 except Exception as e:
55 print('*** Caught exception: %s: %s' % (e.__class__, e))
56 traceback.print_exc()
57 try:
58 client.close()
59 except:
60 pass
61 sys.exit(1)

1 #!/usr/bin/env python
2 # -*- coding:utf-8 -*-
3 # Author:Uson
4
5 import yaml
6 '''约定俗成'''
7 try:
8 from yaml import CLoader as Loader, CDumper as Dumper
9 except ImportError:
10 from yaml import Loader, Dumper
11
12 def print_err(msg, quit=False):
13 output = "\033[31;1mError:%s\033[0m" %msg
14 if quit:
15 exit(output)
16 else:
17 print(output)
18
19 def yaml_parser(yml_filename):
20 '''
21 load yaml file and return
22 :param yml_filename:
23 :return:
24 '''
25 '''
26 #未创建hostgroup和bindhost,却给他们关联数据,会报错
27 #但会成功创建userprofile表
28 #'gbk' codec can't decode byte 0xaa in position 4: illegal multibyte sequence?
29 # yml 不支持添加中文注释
30 '''
31 # yml_filename = "%s/%s.yml" % (settings.StateFileBaseDir,yml_filename)
32 try:
33 print("读取文件")
34 yaml_file = open(yml_filename, 'r')
35 data = yaml.load(yaml_file)
36 print("返回字典数据")
37 return data
38 except Exception as e:
39 print_err(e)

1 #!/usr/bin/env python
2 # -*- coding:utf-8 -*-
3 # Author:Uson
4 '''
5 表示数据库已有此数据
6 [SQL: INSERT INTO hostgroup (name) VALUES (%(name)s)]
7 [parameters: {'name': 'web_devteam'}]
8 (Background on this error at: http://sqlalche.me/e/gkpj)
9 '''
10
11 from models import models
12 from modules.db_conn import engine, session
13 from modules import utils, ssh_login, common_filters
14
15 from models import models
16
17 def welcome_msg(user):
18 WELCOME_MSG = '''\033[32;1m
19 ------------- %s登录成功 -------------
20 \033[0m''' % user.username
21 print(WELCOME_MSG)
22
23 def auth():
24 '''
25 do the user login authentication
26 :return:
27 '''
28 count = 0
29 while count < 3:
30 username = input("\033[32;1m堡垒机用户名:\033[0m").strip()
31 if len(username) == 0: continue
32 password = input("\033[32;1m堡垒机登录密码:\033[0m").strip()
33 if len(password) == 0: continue
34 user_obj = session.query(models.UserProfile).filter(
35 models.UserProfile.username==username,
36 models.UserProfile.password==password).first()
37 #如果数据库存在用户账户,返回用户对象
38 print("User_obj>>", user_obj)
39 if user_obj:
40 return user_obj
41 else:
42 # print("错误的用户名或密码,您还有%s次输入机会。" % (3 - (count + 1)))
43 print("错误的用户名或密码,您还有%s次输入机会。" % (3 - count - 1))
44 count += 1
45 else:
46 utils.print_err("登录错误次数过多,请稍后再试。")
47
48 def hand_modify(argvs):
49 '''
50 格式:hand_modify -f 功能函数(create_remoteusers) modify_id modify_name modified_name
51 :param argvs:
52 :return:
53 '''
54 if '-f' in argvs:
55 modify_table = argvs[argvs.index("-f") +1 ]
56 modify_id = int(argvs[argvs.index("-f") + 2])
57 modify_name = argvs[argvs.index("-f") + 3]
58 modified_name = argvs[argvs.index("-f") + 4]
59 print("修改》》", modify_id, modify_table, modified_name, modify_name)
60 if modify_table == 'create_hosts':
61 pass
62 # from models.models import Host
63 # session.query(Host).filter(Host.modify_name)
64 elif modify_table == 'create_remoteusers':
65 # from models import models
66 print("modify_id:", modify_id)
67 obj = session.query(models.RemoteUser).filter(
68 models.RemoteUser.id==modify_id).first()
69 # print("obj:", obj)
70 # print("obj:")
71 if modify_name == 'password':
72 obj.password = modified_name
73 elif modify_name == 'auth_type':
74 print("auth_type>>>\n", obj.auth_type)
75 obj.auth_type = modified_name
76 session.add(obj)
77 elif modify_table == 'create_users':
78 from models.models import UserProfile
79 elif modify_table == 'create_groups':
80 from models.models import HostGroup
81 else:
82 utils.print_err("invalid usage, should be:\n"
83 "hand_modify -f 功能函数 modify_id modify_name modified_name",quit=True)
84 print("hostgroup正在创建……")
85 session.commit()
86 print("hostgroup创建成功!")
87
88 # source = utils.yaml_parser(modify_table)
89 # print("modify_file - source:", source)
90 # if source:
91 # for key,val in source.items():
92 # print("key,val:", key,val)
93 #
94 # #判断key是否存在,存在,退出
95 # hostgroup = session.query(models.HostGroup).filter(
96 # models.HostGroup.name==key).first()
97 # if hostgroup:
98 # utils.print_err("该主机组已存在,不可重复添加。", quit = True)
99 # obj = models.HostGroup(name=key) #实例化主机组
100 # print("obj:", obj)
101 # session.add(obj)
102 # print("hostgroup正在创建……")
103 # session.commit()
104 # print("hostgroup创建成功!")
105
106 def stop_server():
107 pass
108
109 def log_recording(user_obj,bind_host_obj,logs):
110 '''
111 flush user operations on remote host into DB
112 :param user_obj:
113 :param bind_host_obj:
114 :param logs: list format [logItem1,logItem2,...]
115 :return:
116 '''
117 print("\033[41;1m--logs:\033[0m",logs)
118 session.add_all(logs)
119 session.commit()
120
121 def start_session(argvs): #登录认证,开启堡垒机会话操作远程主机
122 print('堡垒机会话登录认证!')
123 user = auth()
124 '''返回用户登录对象user_obj'''
125 if user:
126 welcome_msg(user) #类user. #登录成功
127 '''打印主机分组信息'''
128 print("user.bindhosts:", user.bindhosts) #hosts.ip,remoteusers.username,remoteusers.auth_type
129 print(user.hostgroups) #name
130 exit_flag = False
131 while not exit_flag:
132 #未分组的主机列表
133 if user.bindhosts:
134 print('\033[32;1mz.未分组主机 (%s)\033[0m' %len(user.bindhosts))
135 #分组的主机列表
136 for index, group in enumerate(user.hostgroups):
137 print('\033[32;1m%s.\t%s (%s)\033[0m' %
138 (index, group.name, len(group.bindhosts)))
139 print("选择编号查看主机列表》》")
140 choice = input("[%s]:" % user.username).strip()
141 if len(choice) == 0: continue
142 if choice == 'z':
143 print("------ 分组: 未分组主机 ------")
144 for index, bind_u_host in enumerate(user.bindhosts): #user.bindhosts是一个列表
145 print(" %s.\t%s@%s(%s)" % (index,
146 bind_u_host.remoteusers.username, #远程主机用户名
147 bind_u_host.hosts.hostname, #远程主机名
148 bind_u_host.hosts.ip, #远程主机ip地址
149 ))
150 print("----------- END -----------")
151
152 while not exit_flag:
153 user_option = input("[(b)返回, (q)退出, 选择远程主机进行登录]:").strip()
154 if len(user_option) == 0: continue
155 if user_option == 'b': break
156 if user_option == 'q': exit_flag = True
157 if user_option.isdigit():
158 user_option = int(user_option)
159 # if type(choice) == str:
160 # choice = int(choice)
161 if user_option < len(user.bindhosts):
162 print('host:', user.bindhosts[user_option])
163 '''日志表还没创建'''
164 print('audit log:', user.bindhosts[user_option].audit_logs)
165 '''接下来,登录远程主机操作'''
166 ssh_login.ssh_login(user,
167 user.bindhosts[user_option],
168 session,
169 log_recording) # 这里只是传函数地址过去,并没有执行函数
170 else:
171 print("没有这样的主机编号...")
172
173 elif choice.isdigit(): #输入的是主机分组编号
174 choice = int(choice)
175 if choice < len(user.hostgroups):
176 print("------ 分组: %s ------" % user.hostgroups[choice].name ) #第choice组
177 for index,bind_host in enumerate(user.hostgroups[choice].bindhosts):
178 print(" %s.\t%s@%s(%s)"%(index,
179 bind_host.remoteusers.username,
180 bind_host.hosts.hostname,
181 bind_host.hosts.ip,
182 ))
183 print("----------- END -----------" )
184
185 #host selection
186 '''选择远程主机进行登录'''
187 while not exit_flag:
188 user_option = input("[(b)返回, (q)退出, 选择远程主机进行登录]:").strip()
189 if len(user_option)==0:continue
190 if user_option == 'b':break
191 if user_option == 'q':exit_flag=True
192 if user_option.isdigit():
193 user_option = int(user_option)
194 # if type(choice) == str:
195 # choice = int(choice)
196 if user_option < len(user.hostgroups[choice].bindhosts):
197 print('host:',user.hostgroups[choice].bindhosts[user_option])
198 '''日志表还没创建'''
199 print('audit log:',user.hostgroups[choice].bindhosts[user_option].audit_logs)
200 '''接下来,登录远程主机操作'''
201 ssh_login.ssh_login(user,
202 user.hostgroups[choice].bindhosts[user_option],
203 session,
204 log_recording) #这里只是传函数地址过去,并没有执行函数
205 else:
206 print("没有这样的主机编号...")
207 else:
208 print("没有这样的分组编号...")
209
210 '''后来的函数往上写'''
211 def create_bindhosts(argvs):
212 '''
213 create bind hosts
214 :param argvs:
215 :return:
216 '''
217 if '-f' in argvs:
218 bindhosts_file = argvs[argvs.index("-f") +1 ]
219 else:
220 utils.print_err("invalid usage, should be:\ncreate_hosts -f <the new bindhosts file>",quit=True)
221 source = utils.yaml_parser(bindhosts_file)
222 print("create_bindhosts - source:", source)
223 if source:
224 for key,val in source.items():
225 print("key,val:\n", key,val)
226 host_obj = session.query(models.Host).filter(models.Host.hostname==val.get('hostname')).first()
227 assert host_obj #断言,host_obj必须存在
228
229 # print("get:\n",val.get('remote_users'))
230 # print("val:\n",val['remote_users']) #一模一样
231 # print("val2:\n",val['hostname'])
232
233 for item in val['remote_users']: #是循环列表,而不再是循环字典了new_bindhosts.yml
234 # for item in val.get('remote_users'):
235 print("item:", item ) #列表第一个元素[0]是字典类型 = 字典dict:item{}
236 assert item.get('auth_type') #item{}
237 if item.get('auth_type') == 'ssh-password':
238 remoteuser_obj = session.query(models.RemoteUser).filter(
239 models.RemoteUser.username==item.get('username'),
240 models.RemoteUser.password==item.get('password')
241 ).first()
242 else:
243 remoteuser_obj = session.query(models.RemoteUser).filter(
244 models.RemoteUser.username==item.get('username'),
245 models.RemoteUser.auth_type==item.get('auth_type'),
246 ).first()
247 if not remoteuser_obj:
248 utils.print_err("RemoteUser obj %s does not exist." % item, quit=True)
249 bindhost_obj = models.BindHost(host_id=host_obj.id,remoteuser_id=remoteuser_obj.id)
250 session.add(bindhost_obj) #还没完,还有两个需要一起绑定了,再提交
251
252 #for groups this host binds to
253 #判断source[key]与val是否相同 #是相同的
254 # print("source[key]:\n", source[key])
255 # print("val:\n", val)
256 if source[key].get('groups'):
257 #获取HostGroup类下这个source[key].get('groups')列表中的任意一个,key下面多个元素,以列表形式存放
258 group_objs = session.query(models.HostGroup).filter(
259 models.HostGroup.name.in_(source[key].get('groups') )).all()
260 assert group_objs
261 print('group_objs>>', group_objs) #列表:[log_devteam]
262 bindhost_obj.host_groups = group_objs #反查,第三张表建立多对多关系,需要列表赋值,同Author与Book
263 #for user_profiles this host binds to
264 '''同上'''
265 if source[key].get('user_profiles'):
266 userprofile_objs = session.query(models.UserProfile).filter(
267 models.UserProfile.username.in_(source[key].get('user_profiles') )).all()
268 # print("userprofile_objs1:", userprofile_objs) # []
269 assert userprofile_objs
270 print("userprofile_objs:",userprofile_objs) #[]
271 bindhost_obj.user_profiles = userprofile_objs #从BindHost反查堡垒机用户UserProfile
272 print(bindhost_obj)
273 print("bindhost正在创建……")
274 session.commit()
275 print("bindhost创建成功!")
276
277 def create_groups(argvs):
278 '''
279 create groups
280 :param argvs:
281 :return:
282 '''
283 if '-f' in argvs:
284 group_file = argvs[argvs.index("-f") +1 ]
285 else:
286 utils.print_err("invalid usage, should be:\ncreategroups -f <the new groups file>",quit=True)
287 source = utils.yaml_parser(group_file)
288 print("create_groups - source:", source)
289 if source:
290 for key,val in source.items():
291 print(key,val)
292 obj = models.HostGroup(name=key) #实例化主机组
293 if val.get('bind_hosts'):
294 bind_hosts = common_filters.bind_hosts_filter(val)
295 obj.bind_hosts = bind_hosts
296
297 if val.get('user_profiles'): #add_groups2.yml
298 user_profiles = common_filters.user_profiles_filter(val)
299 obj.user_profiles = user_profiles
300 session.add(obj)
301 print("hostgroup正在创建……")
302 session.commit()
303 print("hostgroup创建成功!")
304
305 def create_users(argvs): #堡垒机用户创建
306 if '-f' in argvs:
307 user_file = argvs[argvs.index("-f") +1 ]
308 else:
309 utils.print_err("invalid usage, should be:\ncreate_users -f <the new users file>", quit=True)
310 source = utils.yaml_parser(user_file)
311 print("create_users - source:", source)
312 if source:
313 for key,val in source.items():
314 print("key, val:", key,val)
315 print("key应该是类似:\nalex\nval应该是类似:\npassword: alex123\n"
316 # "groups:- web_servers\t- db_servers")
317 "groups:web_servers\tdb_servers") # - 被自动切除
318 obj = models.UserProfile(username=key, password=val.get('password')) #实例化堡垒机用户
319 #因为我是先创建的堡垒机用户,组还没有创建,所以第三张表Userprofile_To_Hostgroup无法关联
320
321 #堡垒机用户alex有主机组的情况下
322 if val.get('groups'):
323 groups_obj = session.query(models.HostGroup).filter(
324 models.HostGroup.name.in_(val.get('groups'))).all()
325 #获取HostGroup下所有主机组名,并判断是否在groups[]中,如果在,关联,不在报错
326 # 即判断groups下主机组名是否存在主机组表中,如果在,全部获取出来
327 print("所有主机组名 - groups_obj:", groups_obj)#[mysql_devteam, web_devteam]
328 if not groups_obj: #[]空
329 utils.print_err("none of [%s] exist in group table." % val.get('groups'), quit=True)
330 # 本次明显找不到主机组名,还没创建呢,怎么关联
331 obj.hostgroups = groups_obj #[]全部添加进去
332 print("主机组名不存在,直接退出,应该不会执行到这里。userprofile数据也不会被创建")
333
334 # 堡垒机用户alex有bindhost的情况下
335 if val.get('bind_hosts'):
336 bind_hosts = common_filters.bind_hosts_filter(val)
337 print("bind_hosts:", bind_hosts)
338 print("obj.bindhosts:", obj.bindhosts)
339 obj.bindhosts = bind_hosts
340 print("堡垒机用户obj:", obj)
341
342 session.add(obj)
343 print("user正在创建……")
344 session.commit()
345 print("user创建成功!")
346
347 def create_remoteusers(argvs):# 创建远程主机用户账户
348 if '-f' in argvs:
349 remoteusers_file = argvs[argvs.index("-f") +1 ]
350 else:
351 utils.print_err("invalid usage, should be:\ncreate_remoteusers -f <the new remoteusers file>", quit=True)
352 source = utils.yaml_parser(remoteusers_file)
353 print("create_remoteusers - source>>", source) # 已经把文件内容转成了字典格式
354 if source:
355 for key,val in source.items():
356 print("key,val:", key, val)
357 obj = models.RemoteUser(username=val.get('username'),auth_type=val.get('auth_type'),password=val.get('password'))
358 # obj = models.RemoteUser(username=val.get('username'),auth_type=val.get('auth_type'),
359 # password=val.get('password') or '已设定密钥登录')
360 session.add(obj) #username
361 print("remoteusers OBJ:username>>>>>", obj)
362 print("remoteusers正在创建……")
363 session.commit()
364 print("remoteusers创建成功!")
365
366 def create_hosts(argvs):# 创建远程主机信息
367 if '-f' in argvs:
368 hosts_file = argvs[argvs.index("-f") +1 ] #-f hosts.yml主机信息字典文件
369 else:
370 utils.print_err("不合法的命令参数,应该这样写:\ncreate_hosts -f 新主机信息文件名", quit=True)
371 source = utils.yaml_parser(hosts_file) #把host文件名传过去 错误提示:因为判断else情况下,hosts_file不存在
372 print("create_hosts - source>>", source) #已经把文件内容转成了字典格式
373 if source:
374 for key,val in source.items(): #循环字典内容
375 print("key,val:", key,val)
376 #'''实例化远程主机'''
377 host_obj = models.Host(hostname=key, ip=val.get('ip_addr'), port=val.get('port') or 22)
378 print("实例化对象host_obj:", host_obj) #Host return - key: server1
379 session.add(host_obj)
380 print("hosts正在创建……")
381 session.commit()
382 print("hosts创建成功!")
383 else:
384 utils.print_err("host主机文件为空", quit = True)
385
386 def syncdb(argvs):#传来的是一个列表,所以不需要用非固定参数来了
387 # def syncdb(*args): #非固定参数用法
388 print("正在进行表结构创建....")
389 models.Base.metadata.create_all(engine) # 创建所有表结构
390 print("\033[32;1m表结构创建完成。\033[0m")

1 #!/usr/bin/env python
2 # -*- coding:utf-8 -*-
3 # Author:Uson
4 from models import models
5 from modules.db_conn import engine, session
6 from modules import utils, ssh_login, common_filters
7
8 def add_groups(argvs):
9 '''
10 add groups
11 :param argvs:
12 :return:
13 '''
14 if '-f' in argvs:
15 group_file = argvs[argvs.index("-f") +1 ]
16 else:
17 utils.print_err("invalid usage, should be:\nadd_groups -f <the new groups file>",quit=True)
18 source = utils.yaml_parser(group_file)
19 print("add_groups - source:", source)
20 if source:
21 for key,val in source.items():
22 print(key,val)
23
24 #判断key是否存在,存在,退出
25 hostgroup = session.query(models.HostGroup).filter(
26 models.HostGroup.name==key).first()
27 if hostgroup:
28 utils.print_err("该主机组已存在,不可重复添加。", quit = True)
29 obj = models.HostGroup(name=key) #实例化主机组
30 print("obj:", obj)
31 # if val.get('bind_hosts'):
32 # bind_hosts = common_filters.bind_hosts_filter(val)
33 # obj.bind_hosts = bind_hosts
34 #
35 # if val.get('user_profiles'):
36 # user_profiles = common_filters.user_profiles_filter(val)
37 # obj.user_profiles = user_profiles
38 session.add(obj)
39 print("hostgroup正在创建……")
40 session.commit()
41 print("hostgroup创建成功!")
结构分析构造:

1 阶段分析》》》 2 表结构1:models-v1.py 3 初始化表结构,确定之间的关系(多对多,一对多,一对一)及数量(几个类class) 4 涉及用户名密码联合唯一 5 实现主机到远程用户的多对多关联:models-v1-1.py 6 问题来了:主机和主机组实现多对多关联,如果我只想给alex(属于bj_group)192.168.1.3下的web访问权限 7 但是现在,多对多关联,bj_group下的用户对应多个主机下的多个远程用户登录信息 8 所以,主机和主机组不能直接关联 9 host host_group user 10 192.168.1.1 bj_group root 11 192.168.1.3 bj_group web 12 192.168.1.3 bj_group log 13 192.168.1.2 sh_group mysql 14 15 表结构4:models-v2.py 16 删除:主机到远程用户的多对多关联 17 一个主机ip对应多个不同账户name_pwd 18 一个账户name_pwd对应多个主机ip 19 一个主机ip对应多个主机组group 20 一个主机组group对应多个主机ip 21 双向一对多,就是多对多(三者联合唯一) 22 name pwd ip host_group 23 root abc 1.2.3.4 bj_group 24 mysql acd 1.2.3.4 sh_group 25 web aef 1.6.3.4 bj_group 26 root abc 1.4.7.7 sh_group 27 28 表结构5:models-v3.py 29 '''注意:外键关联:实现堡垒机用户的多对多关联(第三张表)''' 30 '''问题来了:每登录一台主机,都要带着一个主机分组,那么未分组主机就不能登录 31 未分组主机【10】 是不在该用户权限下的广州分组中的其中10个主机 32 北京分组【40】 33 上海分组【100】 34 --------------- 35 广州分组【200】 36 南京分组【30】 37 ''' 38 39 表结构5-5:models-v4.py (4) 40 Bindhost 与 hostgroup多对多关联 41 42 表结构6:models-v5.py (5)= models.py 43 堡垒机用户userprofile与hostgroup多对多关联

完整代码下载:暂无
