跳板机和堡垒机的区别_Python编程(二十一):堡垒机开发

一、前景介绍

到目前为止,很多公司对堡垒机依然不太感冒,其实是没有充分认识到堡垒机在IT管理中的重要作用,很多人觉得,堡垒机就是跳板机,其实这个认识是不全面的,跳板功能只是堡垒机所具备的功能属性中的其中一项而已,下面简单介绍一下堡垒机的重要性。

堡垒机有以下两个至关重要的功能:

(一)权限管理

当公司的服务器变的越来越多后,需要操作这些服务器的人就肯定不只是一个运维人员,同时也可能包括多个开发人员,那么这么多的人操作业务系统,如果权限分配不当就会存在很大的安全风险,举几个场景例子:

  1. 设想公司有300台Linux服务器,A开发人员需要登录其中5台WEB服务器查看日志或进行问题追踪等事务,同时对另外10台hadoop服务器有root权限,在有300台服务器规模的网络中,按常理来讲你是已经使用了ldap(集中式认证,在windows上叫 ad域)权限统一认证的,你如何使这个开发人员只能以普通用户的身份登录5台web服务器,并且同时允许他以管理员的身份登录另外10台hadoop服务器呢?并且同时他对其它剩下的200多台服务器没有访问权限。
  2. 目前,可能很多公司的运维团队为了方便,整个运维团队的运维人员还是共享同一套root密码,这样内部信任机制虽然使大家的工作方便了,但同时存在着极大的安全隐患,很多情况下,一个运维人员只需要管理固定数量的服务器,毕竟公司分为不同的业务线,不同的运维人员管理的业务线也不同,但如果共享一套root密码,其实就等于无限放大了每个运维人员的权限,也就是说,如果某个运维人员想干坏事的话,他可以在几分钟内把整个公司的业务停转,甚至数据都给删除掉。为了降低风险,于是有人想到,把不同业务线的root密码改掉就ok了么,也就是每个业务线的运维人员只知道自己的密码,这当然是最简单有效的方式,但问题是如果你同时用了ldap,这样做又比较麻烦,即使你设置了root不通过ldap认证,那新问题就是,每次有运维人员离职,他所在的业务线的密码都需要重新改一次。

其实上面的问题,可以很简单的通过堡垒机来实现,收回所有人员的直接登录服务器的权限,所有的登录动作都通过堡垒机授权,运维人员或开发人员不知道远程服务器的密码,这些远程机器的用户信息都绑定在了堡垒机上,堡垒机用户只能看到他能用什么权限访问哪些远程服务器。

在回收了运维或开发人员直接登录远程服务器的权限后,其实就等于公司生产系统的所有认证过程都通过堡垒机来完成了,堡垒机等于成了生产系统的SSO(single sign on)模块了。你只需要在堡垒机上添加几条规则就能实现以下权限控制了:

  1. 允许A开发人员通过普通用户登录5台web服务器,通过root权限登录10台hadoop服务器,但对其余的服务器无任务访问权限
  2. 多个运维人员可以共享一个root账户,但是依然能分辨出分别是谁在哪些服务器上操作了哪些命令,因为堡垒机账户是每个人独有的,也就是说虽然所有运维人员共享了一同一个远程root账户,但由于他们用的堡垒账户都是自己独有的,因此依然可以通过堡垒机控制每个运维人员访问不同的机器。

(二)审计管理

审计管理其实很简单,就是把用户的所有操作都纪录下来,以备日后的审计或者事故后的追责。在纪录用户操作的过程中有一个问题要注意,就是这个纪录对于操作用户来讲是不可见的,什么意思?就是指,无论用户愿不愿意,他的操作都会被纪录下来,并且,他自己如果不想操作被纪录下来,或想删除已纪录的内容,这些都是他做不到的,这就要求操作日志对用户来讲是不可见和不可访问的,通过堡垒机就可以很好的实现。

二、堡垒机架构

堡垒机的主要作用权限控制和用户行为审计,堡垒机就像一个城堡的大门,城堡里的所有建筑就是你不同的业务系统 , 每个想进入城堡的人都必须经过城堡大门并经过大门守卫的授权,每个进入城堡的人必须且只能严格按守卫的分配进入指定的建筑,且每个建筑物还有自己的权限访问控制,不同级别的人可以到建筑物里不同楼层的访问级别也是不一样的。还有就是,每个进入城堡的人的所有行为和足迹都会被严格的监控和纪录下来,一旦发生犯罪事件,城堡管理人员就可以通过这些监控纪录来追踪责任人。

17caa5e3981001b1e8c00283da308ae6.png

堡垒机要想成功完全记到他的作用,只靠堡垒机本身是不够的, 还需要一系列安全上对用户进行限制的配合,堡垒机部署后,同时要确保你的网络达到以下条件:

  • 所有人包括运维、开发等任何需要访问业务系统的人员,只能通过堡垒机访问业务系统
    • 回收所有对业务系统的访问权限,做到除了堡垒机管理人员,没有人知道业务系统任何机器的登录密码
    • 网络上限制所有人员只能通过堡垒机的跳转才能访问业务系统
  • 确保除了堡垒机管理员之外,所有其它人对堡垒机本身无任何操作权限,只有一个登录跳转功能
  • 确保用户的操作纪录不能被用户自己以任何方式获取到并篡改 

三、堡垒机功能实现需求

(一)业务需求:

  1. 兼顾业务安全目标与用户体验,堡垒机部署后,不应使用户访问业务系统的访问变的复杂,否则工作将很难推进,因为没人喜欢改变现状,尤其是改变后生活变得更艰难
  2. 保证堡垒机稳定安全运行, 没有100%的把握,不要上线任何新系统,即使有100%把握,也要做好最坏的打算,想好故障预案

(二)功能需求:

  1. 所有的用户操作日志要保留在数据库中
  2. 每个用户登录堡垒机后,只需要选择具体要访问的设置,就连接上了,不需要再输入目标机器的访问密码
  3. 允许用户对不同的目标设备有不同的访问权限,例:
    1. 对10.0.2.34 有mysql 用户的权限
    2. 对192.168.3.22 有root用户的权限
    3. 对172.33.24.55 没任何权限
  4. 分组管理,即可以对设置进行分组,允许用户访问某组机器,但对组里的不同机器依然有不同的访问权限

目前市面上的商业堡垒机有:齐治

开源的堡垒机有:jumpserver。使用python语言写的堡垒机。

(三)设计表结构:

表结构如下图所示

61dde8b940799c22c750b52af58bea55.png

四、堡垒机实践

下面用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,该功能是想要查看哪个用户的操作记录,就输入相应人员的名字即可查询到相关的记录
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值