一、前景介绍
到目前为止,很多公司对堡垒机依然不太感冒,其实是没有充分认识到堡垒机在IT管理中的重要作用,很多人觉得,堡垒机就是跳板机,其实这个认识是不全面的,跳板功能只是堡垒机所具备的功能属性中的其中一项而已,下面简单介绍一下堡垒机的重要性。
堡垒机有以下两个至关重要的功能:
(一)权限管理
当公司的服务器变的越来越多后,需要操作这些服务器的人就肯定不只是一个运维人员,同时也可能包括多个开发人员,那么这么多的人操作业务系统,如果权限分配不当就会存在很大的安全风险,举几个场景例子:
- 设想公司有300台Linux服务器,A开发人员需要登录其中5台WEB服务器查看日志或进行问题追踪等事务,同时对另外10台hadoop服务器有root权限,在有300台服务器规模的网络中,按常理来讲你是已经使用了ldap(集中式认证,在windows上叫 ad域)权限统一认证的,你如何使这个开发人员只能以普通用户的身份登录5台web服务器,并且同时允许他以管理员的身份登录另外10台hadoop服务器呢?并且同时他对其它剩下的200多台服务器没有访问权限。
- 目前,可能很多公司的运维团队为了方便,整个运维团队的运维人员还是共享同一套root密码,这样内部信任机制虽然使大家的工作方便了,但同时存在着极大的安全隐患,很多情况下,一个运维人员只需要管理固定数量的服务器,毕竟公司分为不同的业务线,不同的运维人员管理的业务线也不同,但如果共享一套root密码,其实就等于无限放大了每个运维人员的权限,也就是说,如果某个运维人员想干坏事的话,他可以在几分钟内把整个公司的业务停转,甚至数据都给删除掉。为了降低风险,于是有人想到,把不同业务线的root密码改掉就ok了么,也就是每个业务线的运维人员只知道自己的密码,这当然是最简单有效的方式,但问题是如果你同时用了ldap,这样做又比较麻烦,即使你设置了root不通过ldap认证,那新问题就是,每次有运维人员离职,他所在的业务线的密码都需要重新改一次。
其实上面的问题,可以很简单的通过堡垒机来实现,收回所有人员的直接登录服务器的权限,所有的登录动作都通过堡垒机授权,运维人员或开发人员不知道远程服务器的密码,这些远程机器的用户信息都绑定在了堡垒机上,堡垒机用户只能看到他能用什么权限访问哪些远程服务器。
在回收了运维或开发人员直接登录远程服务器的权限后,其实就等于公司生产系统的所有认证过程都通过堡垒机来完成了,堡垒机等于成了生产系统的SSO(single sign on)模块了。你只需要在堡垒机上添加几条规则就能实现以下权限控制了:
- 允许A开发人员通过普通用户登录5台web服务器,通过root权限登录10台hadoop服务器,但对其余的服务器无任务访问权限
- 多个运维人员可以共享一个root账户,但是依然能分辨出分别是谁在哪些服务器上操作了哪些命令,因为堡垒机账户是每个人独有的,也就是说虽然所有运维人员共享了一同一个远程root账户,但由于他们用的堡垒账户都是自己独有的,因此依然可以通过堡垒机控制每个运维人员访问不同的机器。
(二)审计管理
审计管理其实很简单,就是把用户的所有操作都纪录下来,以备日后的审计或者事故后的追责。在纪录用户操作的过程中有一个问题要注意,就是这个纪录对于操作用户来讲是不可见的,什么意思?就是指,无论用户愿不愿意,他的操作都会被纪录下来,并且,他自己如果不想操作被纪录下来,或想删除已纪录的内容,这些都是他做不到的,这就要求操作日志对用户来讲是不可见和不可访问的,通过堡垒机就可以很好的实现。
二、堡垒机架构
堡垒机的主要作用权限控制和用户行为审计,堡垒机就像一个城堡的大门,城堡里的所有建筑就是你不同的业务系统 , 每个想进入城堡的人都必须经过城堡大门并经过大门守卫的授权,每个进入城堡的人必须且只能严格按守卫的分配进入指定的建筑,且每个建筑物还有自己的权限访问控制,不同级别的人可以到建筑物里不同楼层的访问级别也是不一样的。还有就是,每个进入城堡的人的所有行为和足迹都会被严格的监控和纪录下来,一旦发生犯罪事件,城堡管理人员就可以通过这些监控纪录来追踪责任人。
堡垒机要想成功完全记到他的作用,只靠堡垒机本身是不够的, 还需要一系列安全上对用户进行限制的配合,堡垒机部署后,同时要确保你的网络达到以下条件:
- 所有人包括运维、开发等任何需要访问业务系统的人员,只能通过堡垒机访问业务系统
- 回收所有对业务系统的访问权限,做到除了堡垒机管理人员,没有人知道业务系统任何机器的登录密码
- 网络上限制所有人员只能通过堡垒机的跳转才能访问业务系统
- 确保除了堡垒机管理员之外,所有其它人对堡垒机本身无任何操作权限,只有一个登录跳转功能
- 确保用户的操作纪录不能被用户自己以任何方式获取到并篡改
三、堡垒机功能实现需求
(一)业务需求:
- 兼顾业务安全目标与用户体验,堡垒机部署后,不应使用户访问业务系统的访问变的复杂,否则工作将很难推进,因为没人喜欢改变现状,尤其是改变后生活变得更艰难
- 保证堡垒机稳定安全运行, 没有100%的把握,不要上线任何新系统,即使有100%把握,也要做好最坏的打算,想好故障预案
(二)功能需求:
- 所有的用户操作日志要保留在数据库中
- 每个用户登录堡垒机后,只需要选择具体要访问的设置,就连接上了,不需要再输入目标机器的访问密码
- 允许用户对不同的目标设备有不同的访问权限,例:
- 对10.0.2.34 有mysql 用户的权限
- 对192.168.3.22 有root用户的权限
- 对172.33.24.55 没任何权限
- 分组管理,即可以对设置进行分组,允许用户访问某组机器,但对组里的不同机器依然有不同的访问权限
目前市面上的商业堡垒机有:齐治
开源的堡垒机有:jumpserver。使用python语言写的堡垒机。
(三)设计表结构:
表结构如下图所示
四、堡垒机实践
下面用python语言开发自己的堡垒机
(一)先了解Python的paramiko模块
该模块其实也实现了简单的堡垒机功能
在http://github.com上下载paramiko的源代码
将源码包中的文件夹demos拷贝出来,在命令行下进入demos文件夹,执行命令:
python demo.py
提示输入主机名(IP地址),接下来输入密码和用户名登录主机,此时会出错
这个源代码不支持python 3
提示错误在:demosinteractive.py", line 87, in writeall
TypeError: write() argument must be str, not bytes
此时修改源代码,源代码是:sys.stdout.write(data)
修改为:sys.stdout.write(data.decode())
修改源代码后,此时可以正常连接Linux主机
下面在Linux主机上进行操作
使用sz和rz命令发送和接收文件
解压文件:unzip paramiko-master.zip
cd paramiko-master,cd demos
python3 demo.py
修改源文件interactive.py,的代码段如下:
cmd = [] # 修改
while True:
r, w, e = select.select([chan, sys.stdin], [], [])
if chan in r:
try:
x = u(chan.recv(1024))
if len(x) == 0:
sys.stdout.write("rn*** EOFrn")
break
sys.stdout.write(x)
sys.stdout.flush()
except socket.timeout:
pass
if sys.stdin in r:
x = sys.stdin.read(1)
if len(x) == 0:
break
if x == "r": # 修改
cmd_str="".join(cmd) # 修改
print("-->",cmd_str) # 修改
cmd = [] # 修改
else: # 修改
cmd.append(x) # 修改
chan.send(x)
修改源代码后,重新执行
python3 demo.py
登录Linux主机,可从输出看见每条被执行的命令。
例如登录后,执行命令ls,在输出命令执行结果前,输出:ls--> ls
现在需要实现远程用户登录堡垒机,就执行demo.py文件远程连接需要连接的主机
由于堡垒机上每个用户都有家目录,家目录下有个文件是.bashrc,将demo.py文件的绝对路径添加到用户家目录下的.bashrc文件中,在末尾添加这条命令:
python3 /paramiko-master/demos/demo.py
这样每个用户登录堡垒机时就自动执行demo.py去远程连接其它主机
source .bashrc # 使文件立即生效
(二)使用Python开发自己的堡垒机
下面编写自己的堡垒机,将每个用户执行的每个命令都记录在数据库中
首先为项目建一个文件夹:testJumpServer
在testJumpServer目录下分别创建python package目录:bin,conf,log,models,以及普通目录modules, shareexamples
在bin目录下创建文件:test_it.py,该文件是主程序文件
在conf目录下创建文件:settings.py, action_registers.py
在models目录下创建文件:models.py文件,在存放表结构
modules目录下的文件有:actions.py, db_conn.py,utils.py,views.py
shareexamples目录下的文件有:new_bindhosts.yml, new_groups.yml, new_hosts.yml, new_remoteuser.yml, new_user.yml
在创建表结构之前,先了解需求:
假设有堡垒机用户:michael,需要登录下面主机:
michael 192.168.1.10 root
michael 192.168.1.11 mysql
jack 192.168.1.10 root
jack 10.10.22.11 mysql
jack 10.10.22.11 web
从上面需求可以看出:一个用户可以对应多个主机,一个主机可以对应多个用户,一个用户可以在同一台主机上有多个账户,
首先创建一张表保存远程主机的信息:host表,字段:ip hostname port
创建保存堡垒机上的用户名和密码信息:user_profile表:字段:username password
再创建一张保存远程主机的用户名和密码信息:remote_user表:
字段:username password auth_type(密码还是ssk_key)
root abc ssh-password
mysql ssh-key
mysql abc ssh-password
创建主机组表:host_group
字段:name
创建日志表:auditlog
字段:date 堡垒机用户 cmd ip remote_user
下面代码是 test_it.py 文件的代码:
Author: __Cheng__
import os,sys
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
print(BASE_DIR)
sys.path.append(BASE_DIR)
if __name__ == '__main__':
from modules.actions import excute_from_command_line
excute_from_command_line(sys.argv)
所有的源代码如下:
# filename:settings.py
Author: __Cheng__
ConnParams = "mysql+pymysql://michael:michael123@192.168.0.50:3508/testJPdb?charset=utf8"
# filename:action_registers.py
Author: __Cheng__
from modules import views
actions = {
'start_session': views.start_session,
# 'stop': views.stop_server,
'syncdb': views.syncdb,
'create_users': views.create_users,
'create_groups': views.create_groups,
'create_hosts': views.create_hosts,
'create_bindhosts': views.create_bindhosts,
'create_remoteusers': views.create_remoteusers,
'audit': views.log_audit,
}
# filename:models.py
Author: __Cheng__
# 定义表结构
from sqlalchemy import Table, Column, Enum,Integer,String,DATE, ForeignKey,UniqueConstraint
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base
# 安装一个插件pip3 install sqlalchemy_utils
# PasswordType方法可以把密码加密,但不好使
from sqlalchemy_utils import ChoiceType,PasswordType
from sqlalchemy import create_engine
# from sqlalchemy.orm import sessionmaker
Base = declarative_base()
# 创建多对多关系,主机和远程用户的关联
user_m2m_bindhost = Table('user_m2m_bindhost', Base.metadata,
Column('userprofile_id',Integer,ForeignKey('user_profile.id')),
Column('bindhost_id',Integer,ForeignKey('bind_host.id')),
)
bindhost_m2m_hostgroup = Table('bindhost_m2m_hostgroup', Base.metadata,
Column('bindhost_id',Integer,ForeignKey('bind_host.id')),
Column('hostgroup_id',Integer,ForeignKey('host_group.id')),
)
user_m2m_hostgroup = Table('userprofile_m2m_hostgroup', Base.metadata,
Column('userprofile_id',Integer,ForeignKey('user_profile.id')),
Column('hostgroup_id',Integer,ForeignKey('host_group.id')),
)
class Host(Base):
__tablename__ = 'host'
id = Column(Integer, primary_key=True)
hostname = Column(String(64), unique=True)
ip = Column(String(64))
port = Column(Integer, default=22)
#remote_users = relationship('RemoteUser', secondary=host_m2m_remoteuser, backref='Hosts')
def __repr__(self):
return self.hostname
class HostGroup(Base):
__tablename__ = 'host_group'
id = Column(Integer, primary_key=True)
name = Column(String(64), unique=True)
bind_hosts = relationship("BindHost", secondary="bindhost_m2m_hostgroup", backref="host_groups")
def __repr__(self):
return self.name
class RemoteUser(Base):
__tablename__ = 'remote_user'
# 用户名和密码要联合唯一,下面命令中指出'auth_type', 'username', 'password'三个字段要联合唯一,通过哈希算法存储在'_user_passwd_uc'中
__table_args__ = (UniqueConstraint('auth_type', 'username', 'password', name='_user_passwd_uc'),)
id = Column(Integer, primary_key=True)
# auth_type = Column(Enum(0, 1)) # 不采用这种方法
AuthTypes = [
('ssh-password', 'SSH/Password'),
('ssh-key', 'SSH/KEY'),
] # 列表中有两个元组,每个元组的两个元素是映射关系,如:ssh-key是存入MYSQL的值,SSH/KEY是查询的显示,可以写中文
auth_type = Column(ChoiceType(AuthTypes))
username = Column(String(32))
password = Column(String(128))
def __repr__(self):
return self.username
class BindHost(Base):
"""
存放下面的关系
192.168.1.10 web bj_group
192.168.1.11 mysql sh_group
"""
__tablename__='bind_host'
__table_args__ = (UniqueConstraint('host_id', 'remoteuser_id', name='_host_remoteuser_uc'),) # 'group_id',不要了
id = Column(Integer, primary_key=True)
host_id = Column(Integer, ForeignKey('host.id'))
#group_id = Column(Integer, ForeignKey('group.id'))
remoteuser_id = Column(Integer, ForeignKey('remote_user.id'))
host = relationship('Host',backref="bind_hosts")
#host_group = relationship("Guoup",backref="bind_hosts")
remote_user = relationship("RemoteUser", backref="bind_hosts")
def __repr__(self):
return "<%s -- %s>" % (self.host.ip,
self.remote_user.username)
# self.host_group.name)
class UserProfile(Base):
"""堡垒机账户"""
__tablename__ = 'user_profile'
id = Column(Integer, primary_key=True)
username = Column(String(32), unique=True)
password = Column(String(128))
bind_hosts = relationship("BindHost", secondary="user_m2m_bindhost", backref="user_profiles")
host_groups = relationship("HostGroup", secondary="userprofile_m2m_hostgroup", backref="user_profiles")
def __repr__(self):
return self.username
# class AuditLog(Base):
# pass
if __name__ == "__main__":
engine = create_engine("mysql+pymysql://michael:michael123@192.168.0.50:3508/goodmandb?charset=utf8",
encoding='utf-8')
Base.metadata.create_all(engine) # 创建表结构
# filename: actions.py
Author: __Cheng__
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):
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)
action_registers.actions[argvs[1]](argvs[1:])
# filename: db_conn.py
Author: __Cheng__
from sqlalchemy import create_engine,Table
from sqlalchemy.orm import sessionmaker
from conf import settings
engine = create_engine(settings.ConnParams)
#engine = create_engine(settings.DB_CONN,echo=True)
SessionCls = sessionmaker(bind=engine) #创建与数据库的会话session class ,注意,这里返回给session的是个class,不是实例
session = SessionCls()
# filename: utils.py
Author: __Cheng__
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):
output = "033[31;1mError: %s033[0m" % msg
if quit:
exit(output)
else:
print(output)
def yaml_parser(yml_filename):
'''
load yaml file and return
:param yml_filename:
:return:
'''
#yml_filename = "%s/%s.yml" % (settings.StateFileBaseDir,yml_filename)
try:
yaml_file = open(yml_filename,'r')
data = yaml.load(yaml_file)
return data
except Exception as e:
print_err(e)
# filename:views.py
Author: __Cheng__
from models import models
from conf import settings
from modules.utils import yaml_parser, print_err
from modules.db_conn import session
def syncdb(argvs):
print("Syncing DB....")
engine = models.create_engine(settings.ConnParams,
encoding='utf-8')
models.Base.metadata.create_all(engine) #创建所有表结构
def create_hosts(argvs):
'''
创建主机
create hosts
:param argvs:
:return:
'''
if '-f' in argvs:
hosts_file = argvs[argvs.index("-f") +1 ] # 让用户指定文件名
else:
print_err("invalid usage, should be:ncreate_hosts -f <the new hosts file>",quit=True)
source = yaml_parser(hosts_file) # 传递文件名
if source:
# print(source) # 先看source是什么,输出信息如下,就是一个大字典
# {'ubuntu test': {'ip': '192.168.0.10', 'port': 22}, 'server1': {'ip': '192.168.0.50', 'port': 30000}, 'server2': {'ip': '10.4.4.22'}}
# 下面的循环语句就在数据库中插入上面的数据
for key,val in source.items():
print(key,val)
obj = models.Host(hostname=key,ip=val.get('ip'), port=val.get('port') or 22)
session.add(obj)
session.commit()
def create_remoteusers(argvs):
'''
创建远程用户
create remoteusers
:param argvs:
:return:
'''
if '-f' in argvs:
remoteusers_file = argvs[argvs.index("-f") +1 ]
else:
print_err("invalid usage, should be:ncreate_remoteusers -f <the new remoteusers file>",quit=True)
source = yaml_parser(remoteusers_file)
if source:
for key,val in source.items():
print(key,val)
obj = models.RemoteUser(username=val.get('username'),auth_type=val.get('auth_type'),password=val.get('password'))
session.add(obj)
session.commit()
def create_users(argvs):
'''
创建堡垒机用户
create little_finger access user
:param argvs:
:return:
'''
if '-f' in argvs:
user_file = argvs[argvs.index("-f") +1 ]
else:
print_err("invalid usage, should be:ncreateusers -f <the new users file>",quit=True)
source = yaml_parser(user_file)
if source:
for key,val in source.items():
print(key,val)
obj = models.UserProfile(username=key,password=val.get('password'))
# if val.get('groups'):
# groups = session.query(models.Group).filter(models.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 groups
:param argvs:
:return:
'''
if '-f' in argvs:
group_file = argvs[argvs.index("-f") +1 ]
else:
print_err("invalid usage, should be:ncreategroups -f <the new groups file>",quit=True)
source = yaml_parser(group_file)
if source:
for key,val in source.items():
print(key,val)
obj = models.HostGroup(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 bind hosts
:param argvs:
:return:
'''
if '-f' in argvs:
bindhosts_file = argvs[argvs.index("-f") +1 ]
else:
print_err("invalid usage, should be:ncreate_hosts -f <the new bindhosts file>",quit=True)
source = yaml_parser(bindhosts_file)
if source:
for key,val in source.items():
#print(key,val)
host_obj = session.query(models.Host).filter(models.Host.hostname==val.get('hostname')).first()
assert host_obj # 要求主机必须存在
for item in val['remote_users']:
print(item )
assert item.get('auth_type')
if item.get('auth_type') == 'ssh-password':
# 获取远程用户
remoteuser_obj = session.query(models.RemoteUser).filter(
models.RemoteUser.username==item.get('username'),
models.RemoteUser.password==item.get('password')
).first()
else:
remoteuser_obj = session.query(models.RemoteUser).filter(
models.RemoteUser.username==item.get('username'),
models.RemoteUser.auth_type==item.get('auth_type'),
).first()
if not remoteuser_obj:
print_err("RemoteUser obj %s does not exist." % item,quit=True )
bindhost_obj = models.BindHost(host_id=host_obj.id,remoteuser_id=remoteuser_obj.id) # 创建关系,通过查询到的结果来创建
session.add(bindhost_obj)
#for groups this host binds to
if source[key].get('groups'):
# 如果有组,就把组里的所有数据取出来
group_objs = session.query(models.HostGroup).filter(models.HostGroup.name.in_(source[key].get('groups') )).all()
assert group_objs
print('groups:', group_objs)
bindhost_obj.host_groups = group_objs # 这里的host_groups是models.py模块中HostGroup类中的relationship方法中的参数backref参数的值host_groups
#for user_profiles this host binds to
if source[key].get('user_profiles'):
userprofile_objs = session.query(models.UserProfile).filter(models.UserProfile.username.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(models.UserProfile).filter(models.UserProfile.username==username,
models.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):
WELCOME_MSG = '''033[32;1m
------------- Welcome [%s] login testJumpServer -------------
033[0m'''% user.username
print(WELCOME_MSG)
def start_session(argvs):
print('going to start sesssion ')
user = auth() # 认证
if user:
welcome_msg(user)
print(user.bind_hosts)
print(user.host_groups)
exit_flag = False
while not exit_flag:
if user.bind_hosts:
print('033[32;1mz.tungroupped hosts (%s)033[0m' %len(user.bind_hosts) ) # len(user.bind_hosts)显示有多少条未分组的主机
for index,group in enumerate(user.host_groups):
print('033[32;1m%s.t%s (%s)033[0m' %(index,group.name, len(group.bind_hosts)) ) # group.bind_hosts是因为models.py中的HostGroup类有bind_hosts实例
choice = input("[%s]:" % user.username).strip() # 获取用户输入,选择
if len(choice) == 0:continue
if choice == 'z': # 如果输入的是'z',打印未分组的主机信息
print("------ Group: ungroupped hosts ------" )
for index,bind_host in enumerate(user.bind_hosts):
print(" %s.t%s@%s(%s)"%(index,
bind_host.remote_user.username,
bind_host.host.hostname,
bind_host.host.ip,
))
print("----------- END -----------" )
elif choice.isdigit(): # 如果输入的是一个整数
choice = int(choice)
if choice < len(user.host_groups): # 先判断先选择的是哪个组,就打印哪个组
print("------ Group: %s ------" % user.host_groups[choice].name )
for index,bind_host in enumerate(user.host_groups[choice].bind_hosts):
print(" %s.t%s@%s(%s)"%(index,
bind_host.remote_user.username,
bind_host.host.hostname,
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.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)
# 下面是关键部分, 其中log_recording负责把日志写入数据库
# 这里还需要ssh_login.py文件的ssh_login()方法和log_recording()方法,这个留在作业完成
ssh_login.ssh_login(user,
user.host_groups[choice].bind_hosts[user_option],
session,
log_recording)
else:
print("no this option..")
下面是需要写入数据库的文件,内容如下:
# filename: new_bindhosts.yml
bind1:
hostname: ubuntu test
remote_users:
- user1:
username: root
auth_type: ssh-key
#password: 123
- user2:
username: michael
auth_type: ssh-password
password: michael123456
groups:
- bj_group
user_profiles:
- michael
- jack
bind2:
hostname: server2
remote_users:
- user1:
username: root
auth_type: ssh-password
password: abc!1234
groups:
- bj_group
- sh_group
user_profiles:
- Tom
# filename: new_groups.yml
bj_group:
#bind_hosts:
# - h1
# - h2
user_profiles:
- michael
sh_group:
user_profiles:
- jack
- michael
- rain
# filename: new_hosts.yml
ubuntu test:
ip: 192.168.0.10
port: 22
server1:
ip: 192.168.0.50
port: 30000
server2:
ip: 10.4.4.22
# filename: new_remoteusers.yml
user0:
auth_type: ssh-password
username: root
password: abc!1234
user1:
auth_type: ssh-password
username: root
password: qwert123
user2:
auth_type: ssh-key
username: root
#password: abc!23
user3:
auth_type: ssh-password
username: michael
password: michael123456
# filename:new_user.yml
michael:
password: michael123
# groups:
# - web_servers
# - db_servers
#bind_hosts:
# - h1
# - h2
# - h3
jack:
password: jack123
在运行bin目录下的test_it.py代码前,先在数据库创建一个新的数据库testJPdb
create database testJPdb charset utf8;
此时切换到bin目录的命令行下,执行下面的命令
python test_it.py # 执行成功则输出下面信息:
[31;1mAvailable commands:[0m
syncdb
再次执行下面命令:
python test_it.py adjla;kdf # 输出提示信息如下:
[31;1mError: Command [adjla;kdf] does not exist![0m
再一次执行下面的命令:
python test_it.py syncdb # 输出如下提示信息:
Syncing DB.... # 此时数据表才真正的创建成功
在执行上面的创建命令时,如提示权限问题,可在mysql服务器上加权限:
grant all on *.* to "michael"@ "%" identified by "michael123";
flush privileges;
在mysql服务器上验证:
use testJPdb;
show tables; # 输出如下信息:
+---------------------------+
| Tables_in_testJPdb |
+---------------------------+
| bind_host |
| bindhost_m2m_hostgroup |
| host |
| host_group |
| remote_user |
| user_m2m_bindhost |
| user_profile |
| userprofile_m2m_hostgroup |
+---------------------------+
数据表创建完成后,接下来就往数据表中插入数据
下面使用pyyaml模块写入数据,先安装pyyaml模块
安装:pip3 install pyyaml
导入: import yaml
准备工作做完后,在命令行下再次执行执行:
python test_it.py # 后面不加参数,输出如下信息:提示有2个命令可用
[31;1mAvailable commands:[0m
syncdb
create_hosts
接下来执行: python test_it.py creat_hosts # 提示要求输入文件名
[31;1mError: invalid usage, should be:
create_hosts -f <the new hosts file>[0m
下面来测试modules下的views.py文件中的 create_hosts 方法的source变量的输出信息:
执行:python test_it.py create_hosts -f ..shareexamplesnew_hosts.yml # 提示如下信息:
{ 'ubuntu test': { 'ip_addr': '192.168.0.10', 'port': 22}, 'server1': { 'ip_addr': '192.168.0.50', 'port': 30000}, 'server2': { 'ip_addr': '10.4.4.22'}}
提示信息就是一个大的字典
再一次执行:python test_it.py create_hosts -f ..shareexamplesnew_hosts.yml # 提示如下信息:数据写入成功
ubuntu test { 'ip': '192.168.0.10', 'port': 22}
server1 { 'ip': '192.168.0.50', 'port': 30000}
server2 { 'ip': '10.4.4.22'}
此时在Mysql服务器上执行:select * from hosts; # 输出如下信息:
+----+-------------+--------------+-------+
| id | hostname | ip | port |
+----+-------------+--------------+-------+
| 1 | ubuntu test | 192.168.0.10 | 22 |
| 2 | server1 | 192.168.0.50 | 30000 |
| 3 | server2 | 10.4.4.22 | 22 |
+----+-------------+--------------+-------+
下面来创建远程用户,再和主机关联起来
首先在shareexamples目录下创建yaml文件nes_remoteusers.yml, 写入相关的用户信息
其中的user0, user1不用管,后面依次是类型、用户名、密码
其次,在conf目录下的action_registers.py文件中开启create_remoteusers功能
准备工作做完后,就来执行下面的命令:
python test_it.py create_remoteusers -f ..shareexamplesnew_remoteusers.yml
命令执行成功后输出如下信息:
user0 { 'auth_type': 'ssh-password', 'username': 'root', 'password': 'abc!1234'}
user1 { 'auth_type': 'ssh-password', 'username': 'root', 'password': 'qwert123'}
user2 { 'auth_type': 'ssh-key', 'username': 'root'}
user3 { 'auth_type': 'ssh-password', 'username': 'michael', 'password': 'michael123456'}
此时在数据库上执行:select * from remote_user; # 输出如下信息:
+----+--------------+----------+---------------+
| id | auth_type | username | password |
+----+--------------+----------+---------------+
| 11 | ssh-key | root | NULL |
| 12 | ssh-password | michael | michael123456 |
| 9 | ssh-password | root | abc!1234 |
| 10 | ssh-password | root | qwert123 |
+----+--------------+----------+---------------+
下面来创建堡垒机用户:
先开启功能:create_users
准备工作做完后,执行下面命令创建堡垒机用户:
python test_it.py create_users -f ..shareexamplesnew_user.yml
创建成功,输出如下信息:
michael { 'password': 'michael123'}
jack { 'password': 'jack123'}
此时在mysql服务器上执行 select * from user_profile; # 输出如下信息:
+----+----------+------------+
| id | username | password |
+----+----------+------------+
| 1 | michael | michael123 |
| 2 | jack | jack123 |
+----+----------+------------+
下面来创建主机组
开户功能:create_groups
准备工作完成后,执行下面的命令创建组:
python test_it.py create_groups -f ..shareexamplesnew_groups.yml
输出如下信息,表示创建组成功:
bj_group { 'user_profiles': [ 'michael']}
sh_group { 'user_profiles': [ 'jack', 'michael', 'rain']}
此时查询:select * from host_group; # 输出如下信息:
+----+----------+
| id | name |
+----+----------+
| 1 | bj_group |
| 2 | sh_group |
+----+----------+
下面创建关联关系
首先开启功能:create_bindhosts
在mysql上另外增加一个堡垒机用户:
insert into user_profile (username, password) values( "Tom", "tom123");
准备工作做完后,执行下面的命令创建关联关系
python test_it.py create_bindhosts -f ..shareexamplesnew_bindhosts.yml
输出如下信息,表示创建关联关系成功:
{ 'user1': None, 'username': 'root', 'auth_type': 'ssh-key'}
groups: [bj_group]
userprofiles: [michael, jack]
{ 'user2': None, 'username': 'michael', 'auth_type': 'ssh-password', 'password': 'michael123456'}
groups: [bj_group]
userprofiles: [michael, jack]
{ 'user1': None, 'username': 'root', 'auth_type': 'ssh-password', 'password': 'abc!1234'}
groups: [bj_group, sh_group]
userprofiles: [Tom]
此时在Mysql上查询,有如下信息:
select * from bind_host; # 输出如下信息:
+----+---------+---------------+
| id | host_id | remoteuser_id |
+----+---------+---------------+
| 4 | 1 | 11 |
| 5 | 1 | 12 |
| 6 | 3 | 9 |
+----+---------+---------------+
如果程序执行没问题,此时执行下面命令会有相应的输出信息:
select * from bindhost_m2m_hostgroup; # 输出如下:
+-------------+--------------+
| bindhost_id | hostgroup_id |
+-------------+--------------+
| 4 | 1 |
| 5 | 1 |
| 6 | 1 |
| 6 | 2 |
+-------------+--------------+
select * from user_m2m_bindhost; # 输出如下:
+----------------+-------------+
| userprofile_id | bindhost_id |
+----------------+-------------+
| 1 | 4 |
| 2 | 4 |
| 1 | 5 |
| 2 | 5 |
| 3 | 6 |
+----------------+-------------+
如果bindhost_m2m_hostgroup表的关联信息没有创建成功,可以手动创建:
desc bindhost_m2m_hostgroup; # 查看表结构
+--------------+---------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------------+---------+------+-----+---------+-------+
| bindhost_id | int(11) | YES | MUL | NULL | |
| hostgroup_id | int(11) | YES | MUL | NULL | |
+--------------+---------+------+-----+---------+-------+
插入记录:
insert into host_group (name) values( "cd_group");
insert into bindhost_m2m_hostgroup (bindhost_id, hostgroup_id) values(5,3);
此时再执行查夜命令,有如下输出信息:
select * from bindhost_m2m_hostgroup;
+-------------+--------------+
| bindhost_id | hostgroup_id |
+-------------+--------------+
| 4 | 1 |
| 5 | 1 |
| 6 | 1 |
| 6 | 2 |
| 5 | 3 |
+-------------+--------------+
下面继续开启start_session功能
在views.py文件增加auth(),welcome_msg(),start_session()三个函数
此时在命令行下执行: python test_it.py # 会输出下面的信息:
[31;1mAvailable commands:[0m
start_session
syncdb
create_users
create_groups
create_hosts
create_bindhosts
create_remoteusers
再次执行命令:python test_it.py start_session # 提示输入用户名和密码,此时的用户和密码是堡垒机(user_profile表)的用户名和密码
going to start sesssion
[32;1mUsername:[0mmichael # 输入用户名
[32;1mPassword:[0mmichael123 # 输入用户名对应的密码
[32;1m
------------- Welcome [michael] login testJumpServer -------------
[0m
[<192.168.0.10 -- root>, <192.168.0.10 -- michael>]
[]
[32;1mz. ungroupped hosts (2)[0m
[michael]:
从上面的输出可以看出,michael没有跟组关联,下面在数据库中查证一下:
首先:show tables; # 输出如下信息
+---------------------------+
| Tables_in_testJPdb |
+---------------------------+
| bind_host |
| bindhost_m2m_hostgroup |
| host |
| host_group |
| remote_user |
| user_m2m_bindhost |
| user_profile |
| userprofile_m2m_hostgroup |
+---------------------------+
select * from userprofile_m2m_hostgroup; # 查证没有输出信息,确实还没有做关联
下面来做用户和组的关联
首先查看有哪些组:select * from hsot_group; # 输出如下:
+----+----------+
| id | name |
+----+----------+
| 1 | bj_group |
| 3 | cd_group |
| 2 | sh_group |
+----+----------+
看表结构:desc userprofile_m2m_hostgroup; # 输出如下:
+----------------+---------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+----------------+---------+------+-----+---------+-------+
| userprofile_id | int(11) | YES | MUL | NULL | |
| hostgroup_id | int(11) | YES | MUL | NULL | |
+----------------+---------+------+-----+---------+-------+
看堡垒机用户:select * from user_profile;
+----+----------+------------+
| id | username | password |
+----+----------+------------+
| 1 | michael | michael123 |
| 2 | jack | jack123 |
| 3 | Tom | tom123 |
+----+----------+------------+
现在要做michael用户和cd_group组的关联,用户id和组id分别是:1,3,下面来做关联
insert into userprofile_m2m_hostgroup values(1,3); # 执行成功则插入成功
现在重新执行命令:python test_it.py start_session,输入用户和密码后的显示结果:
going to start sesssion
[32;1mUsername:[0mmichael
[32;1mPassword:[0mmichael123
[32;1m
------------- Welcome [michael] login testJumpServer -------------
[0m
[<192.168.0.10 -- root>, <192.168.0.10 -- michael>]
[cd_group]
[32;1mz. ungroupped hosts (2)[0m
[32;1m0. cd_group (1)[0m
[michael]:
从上面的输出可以看出,用户和组已经做好关联,此时cd_group组已经关联了1个主机
此时输入字母z和数字0,可分别看出相关信息:
[michael]:z # 输入字母z
------ Group: ungroupped hosts ------
0. root@ubuntu test(192.168.0.10)
1. michael@ubuntu test(192.168.0.10)
----------- END -----------
[32;1mz. ungroupped hosts (2)[0m
[32;1m0. cd_group (1)[0m
[michael]:0 # 输入数字0
------ Group: cd_group ------
0. michael@ubuntu test(192.168.0.10)
----------- END -----------
[32;1mz. ungroupped hosts (2)[0m
[32;1m0. cd_group (1)[0m
[michael]:
此时在mysql服务器上执行下面命令,给用户michael分配bj_group
insert into userprofile_m2m_hostgroup values(1,1);
此时在命令行退出,重新执行命令:python test_it.py start_session,输出如下信息:
going to start sesssion
[32;1mUsername:[0mmichael
[32;1mPassword:[0mmichael123
[32;1m
------------- Welcome [michael] login testJumpServer -------------
[0m
[<192.168.0.10 -- root>, <192.168.0.10 -- michael>]
[bj_group, cd_group]
[32;1mz. ungroupped hosts (2)[0m
[32;1m0. bj_group (3)[0m # 已经出现了bj_group
[32;1m1. cd_group (1)[0m
[michael]:
此时输入数字0,输出如下信息:michael用户已经关联了不同的组,不同的主机
[michael]:0
------ Group: bj_group ------
0. root@ubuntu test(192.168.0.10)
1. michael@ubuntu test(192.168.0.10)
2. root@server2(10.4.4.22)
----------- END -----------
[32;1mz. ungroupped hosts (2)[0m
[32;1m0. bj_group (3)[0m
[32;1m1. cd_group (1)[0m
[michael]:
上面的操作没有问题,下面就来让用户输入一个数字,选择要连接的主机
首先取消注释views.py文件中的start_session()方法中的while循环语句
后面的代码未完成,留在作业去完成。
要求在action.registers.py文件增加一个功能: 'audit': views.log_audit,该功能是想要查看哪个用户的操作记录,就输入相应人员的名字即可查询到相关的记录