• python作业堡垒机(第十三周)


    作业需求:

    1. 所有的用户操作日志要保留在数据库中

    2. 每个用户登录堡垒机后,只需要选择具体要访问的设置,就连接上了,不需要再输入目标机器的访问密码

    3. 允许用户对不同的目标设备有不同的访问权限,例:

      对10.0.2.34 有mysql 用户的权限

      对192.168.3.22 有root用户的权限

      对172.33.24.55 没任何权限

    4. 分组管理,即可以对设置进行分组,允许用户访问某组机器,但对组里的不同机器依然有不同的访问权限 

    思路解析:

    1.  用户操作日志要保留在数据库中,通过课堂学习对paramiko源码进行修改,在demons/interactive.py 63行中获取用户操作,并将操作记录到数据库中。

    2.  后面的需求使用数据库,建立多对多关联,反向取主机IP,主机密码,对应的堡垒机用户,并划分组内用户权限 ,具体使用sqlalchemy模块对数据库表进行操作。

    3. 针对作业需求,程序添加了查看日志功能,并准许默认用户root查看所有用户操作,其他用户只能查自己下面机器的日志。

    4. 添加了缓存redis减少了数据库IO操作。

    paramiko 用户操作记录源码:

    cmd = []
            while True:
                r, w, e = select.select([chan, sys.stdin], [], [])  # 默认阻塞
                if chan in r:  # 连接建立好了,channle过来有数据了,
                    try:
                        x = u(chan.recv(1024))  # 尝试收数据
                        if len(x) == 0:  # 收数据收不到,
                            sys.stdout.write('
    *** EOF
    ')
                            break
                        sys.stdout.write(x)  # 标准输出
                        sys.stdout.flush()  # flush 怕输出不到,远程发来的数据,远程机器返回
                    except socket.timeout:
                        pass
                if sys.stdin in r:  # 标准输入 活动就能返回到r
                    x = sys.stdin.read(1)
                    if len(x) == 0:
                        break
                    if x == "
    ":
                        cmd_str = "".join(cmd)
                        print("---->",cmd_str)
                        cmd = []
                    else:
                        cmd.append(x)
                    chan.send(x)
    View Code 

    表结构设计图:

      

    README:

    作者:yaobin
    版本: 堡垒机 示例版本 v0.1
    开发环境: python3.6
    
    程序介绍
      1. 所有的用户操作日志要保留在数据库中
      2. 每个用户登录堡垒机后,只需要选择具体要访问的设置,就连接上了,不需要再输入目标机器的访问密码
    
      3. 允许用户对不同的目标设备有不同的访问权限,例:
    
        对10.0.2.34 有mysql 用户的权限
    
        对192.168.3.22 有root用户的权限
    
        对172.33.24.55 没任何权限
    
      4. 分组管理,即可以对设置进行分组,允许用户访问某组机器,但对组里的不同机器依然有不同的访问权限 
    
    文件目录结构
    ├── bin
    │   ├── __init__.py
    │   └── tiny.py  # 主程序
    ├── conf
    │   ├── action_registers.py  # 程序命令交互
    │   ├── __init__.py
    │   ├── __pycache__
    │   │   ├── action_registers.cpython-36.pyc
    │   │   ├── __init__.cpython-36.pyc
    │   │   └── settings.cpython-36.pyc
    │   └── settings.py  # 配置文件
    ├── log
    │   └── __init__.py
    ├── models
    │   ├── __init__.py
    │   ├── models_backup.py  # 备份测试
    │   ├── models.py  # 数据库表模块
    │   ├── __pycache__
    │   │   ├── __init__.cpython-36.pyc
    │   │   └── models.cpython-36.pyc
    │   └── test.py # redis测试
    ├── modules
    │   ├── actions.py  # 欢迎页和程序命令交互
    │   ├── common_filters.py  # 堡垒机用户主机绑定交互
    │   ├── db_conn.py  # mysql连接交互
    │   ├── __init__.py
    │   ├── interactive.py  # ssh传输命令和命令写入交互
    │   ├── __pycache__
    │   │   ├── actions.cpython-36.pyc
    │   │   ├── common_filters.cpython-36.pyc
    │   │   ├── db_conn.cpython-36.pyc
    │   │   ├── __init__.cpython-36.pyc
    │   │   ├── interactive.cpython-36.pyc
    │   │   ├── ssh_login.cpython-36.pyc
    │   │   ├── utils.cpython-36.pyc
    │   │   └── views.cpython-36.pyc
    │   ├── ssh_login.py  # ssh连接交互
    │   ├── utils.py  # yaml配置交互
    │   └── views.py  # 创建表,表数据创建,查看数据库数据交互
    ├── Server.zip
    └── share  
        └── examples
            ├── new_bindhosts.yml  # 主机绑定关系配置文件
            ├── new_groups.yml  # 组创建,组关系绑定配置文件
            ├── new_hosts.yml  # 主机配置文件
            ├── new_remoteusers.yml  # 主机用户名密码配置文件
            └── new_user.yml  # 堡垒机用户配置文件
    View Code

    创建表和使用方法:

    先要创建数据库:
     create database tinytest charset utf8;
    
    1. python3 bin/tiny.py  syncdb
    2. python3 bin/tiny.py create_hosts -f  share/examples/new_hosts.yml
    3. python3 bin/tiny.py create_remoteusers -f share/examples/new_remoteusers.yml 
    4. python3 bin/tiny.py create_users -f share/examples/new_user.yml 
    5. python3 bin/tiny.py create_groups -f share/examples/new_groups.yml
    6. python3 bin/tiny.py create_bindhosts -f share/examples/new_bindhosts.yml
    7. python3 bin/tiny.py start_session
    View Code

    程序核心代码:

    bin

    tiny.py

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    # Time:2017/12/15 21:22
    __Author__ = 'Sean Yao'
    import os
    import 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)
    View Code

    conf

    action_registers

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    # Time:2017/12/14 18:53
    __Author__ = 'Sean Yao'
    from modules import views
    
    actions = {
        'start_session': views.start_session,  # 连接server
        # 'stop': views.stop_server,
        'syncdb': views.syncdb,  # 同步数据
        'create_users': views.create_users,  # 创建users
        'create_groups': views.create_groups,  # 创建组
        'create_hosts': views.create_hosts,  # 创建主机
        'create_bindhosts': views.create_bindhosts,  # 创建绑定关系
        'create_remoteusers': views.create_remoteusers,  # 创建远程用户
        'view_user_record': views.user_record_cmd  # 查看用户操作命令
    }
    View Code

    settings.py

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    # Time:2017/12/14 18:53
    __Author__ = 'Sean Yao'
    # 连接数据库字段
    # ConnParams = "mysql+pymysql://root:123456@192.168.84.66/tinydb?charset=utf8"
    ConnParams = "mysql+pymysql://root:123456@192.168.84.66/tinytest?charset=utf8"
    View Code

    models

    models.py

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    # Time:2017/12/14 19:06
    __Author__ = 'Sean Yao'
    import datetime
    from sqlalchemy import Table, Column, Integer, String, DATE, ForeignKey, Enum, UniqueConstraint, DateTime, Text
    # uniqueconstraint 联合唯一
    from sqlalchemy.orm import relationship
    from sqlalchemy.ext.declarative import declarative_base
    from sqlalchemy_utils import ChoiceType, PasswordType  # sqlalchemy_utils sqalchemy_utils插件
    from sqlalchemy import create_engine
    # from sqlalchemy.orm import sessionmaker
    
    Base = declarative_base()  # 基类
    
    # 多对多关联
    # 关联表堡垒机用户ID和远程主机ID
    user_m2m_bindhost = Table('user_m2m_bindhost', Base.metadata,
                              Column('userprofile_id', Integer, ForeignKey('user_profile.id')),
                              Column('bind_host_id', Integer, ForeignKey('bind_host.id')),)
    # 关联表远程主机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 BindHost(Base):
        '''
        关联关系
        192.168.1.11 web
        192.168.1.11 mysql
        '''
        __tablename__ = "bind_host"
        # 联合唯一
        __table_args__ = (UniqueConstraint('host_id', 'remoteuser_id', name='_host_remoteuser_uc'),)
    
        id = Column(Integer, primary_key=True)
        # 外键
        host_id = Column(Integer, ForeignKey('host.id'))
        remoteuser_id = Column(Integer, ForeignKey('remote_user.id'))
        # 外键关联远程主机,反响查绑定的主机
        host = relationship('Host', backref='bind_hosts')
        # 外键关联堡垒机用户,backref,反向查绑定的堡垒机用户
        remote_user = relationship("RemoteUser", backref='bind_hosts')
    
        def __repr__(self):
            # return "<%s -- %s -- %s>" % (self.host.ip,
            #                              self.remote_user.username,
            #                              self.host_group.name)
    
            return "<%s -- %s >" % (self.host.ip, self.remote_user.username)
    
    class Host(Base):
        '''
        远程主机
        '''
        __tablename__ = 'host'
        id = Column(Integer, primary_key=True)
        hostname = Column(String(64), unique=True)
        ip = Column(String(64), unique=True)
        port = Column(Integer, default=22)
        # 不要让主机关联主机组,这样权限给主机组了,应该是将用户密码和主机组绑定,
        # 比如root 123 sh root 123 bj 这样他可以用所有的权限,
    
        def __repr__(self):
            return self.hostname
    
    class HostGroup(Base):
        '''
        远程主机组
        '''
        __tablename__ = 'host_group'
        id = Column(Integer, primary_key=True)
        name = Column(String(64), unique=True)
        # 通过bindhost_m2m_hostgroup 关联绑定主机和主机组反查到主机组
        bind_hosts = relationship("BindHost", secondary="bindhost_m2m_hostgroup", backref="host_groups")
    
        def __repr__(self):
            return self.name
    
    
    class RemoteUser(Base):
        '''
        远程主机密码表
        '''
        __tablename__ = 'remote_user'
        #  联合唯一,验证类型,用户名密码
        __table_args__ = (UniqueConstraint('auth_type', 'username', 'password', name='_user_passwd_uc'),)
        id = Column(Integer, primary_key=True)
        AuthTypes = [
            ('ssh-password', 'SSH/Password'),  # 第一个是存在数据库里的,第二个具体的值
            ('ssh-key', 'SSH/KEY')
        ]
        auth_type = Column(ChoiceType(AuthTypes))
        username = Column(String(32))
        password = Column(String(128))
    
        def __repr__(self):
            return self.username
    
    class Userprofile(Base):
        '''
        堡垒机用户密码表
        '''
        __tablename__ = 'user_profile'
        id = Column(Integer, primary_key=True)
        username = Column(String(32), unique=True)
        password = Column(String(128))
    
        # 多对多关联通过user_m2m_bindhost关联堡垒机表和主机表能反查到堡垒机用户
        bind_hosts = relationship("BindHost", secondary='user_m2m_bindhost', backref='user_profiles')
        # 多对多关联通过userprofile_m2m_hostgroup关联堡垒机表和组反查到堡垒机用户
        host_groups = relationship("HostGroup", secondary='userprofile_m2m_hostgroup', backref='user_profiles')
    
        def __repr__(self):
            return self.username
    
    class AuditLog(Base):
        '''
        用户操作日志表
        '''
        __tablename__ = 'audit_log'
        id = Column(Integer, primary_key=True)
        user_id = Column(Integer, ForeignKey('user_profile.id'))
        bind_host_id = Column(Integer, ForeignKey('bind_host.id'))
        # # action_choices
        # action_choices = [
        #     (0, 'CMD'),
        #     (1, 'Login'),
        #     (2, 'Logout'),
        #     (3, 'GetFile'),
        #     (4, 'SendFile'),
        #     (5, 'Exception'),
        # ]
        action_choices = [
            (u'cmd', u'CMD'),
            (u'login', u'Login'),
            (u'logout', u'Logout'),
        ]
    
        action_type = Column(ChoiceType(action_choices))
        # 命令可能存的数值更大
        # cmd = Column(String(255))
        cmd = Column(Text(65535))
        date = Column(DateTime)
    
        user_profile = relationship("Userprofile")
        bind_host = relationship("BindHost")
    View Code

    modules

    actions.py

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    # Time:2017/12/15 21:31
    __Author__ = 'Sean Yao'
    
    from conf import action_registers
    from modules import utils
    
    
    def help_msg():
        '''
        print help msgs
        :return:
        '''
        print("33[31;1mAvailable commands:33[0m")
        for key in action_registers.actions:
            print("	", key)
    
    def excute_from_command_line(argvs):
        '''
        print
        :param argvs:
        :return:
        '''
        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)
            # utils 工具箱
        action_registers.actions[argvs[1]](argvs[1:])
    View Code

    common_filters.py

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    # Time:2017/12/18 18:19
    __Author__ = 'Sean Yao'
    from models import models
    from modules.db_conn import engine, session
    from modules.utils import print_err
    
    def bind_hosts_filter(vals):
        '''
    
        :param vals:
        :return:
        '''
        print('**>', vals.get('bind_hosts'))
        bind_hosts = session.query(models.BindHost).filter(models.Host.hostname.in_(vals.get('bind_hosts'))).all()
        if not bind_hosts:
            print_err("none of [%s] exist in bind_host table." % vals.get('bind_hosts'), quit=True)
        return bind_hosts
    
    def user_profiles_filter(vals):
        '''
    
        :param vals:
        :return:
        '''
        user_profiles = session.query(models.Userprofile).filter(models.Userprofile.username.in_(vals.get('user_profiles'))
                                                                 ).all()
        if not user_profiles:
            print_err("none of [%s] exist in user_profile table." % vals.get('user_profiles'), quit=True)
        return user_profiles
    View Code

    db_conn.py

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    # Time:2017/12/15 23:21
    __Author__ = 'Sean Yao'
    from sqlalchemy import create_engine
    from sqlalchemy.orm import sessionmaker
    from conf import settings
    
    engine = create_engine(settings.ConnParams)
    # 创建与数据库的会话session class ,注意,这里返回给session的是个class,不是实例
    SessionCls = sessionmaker(bind=engine)
    session = SessionCls()
    View Code

    interactive.py

    # Copyright (C) 2003-2007  Robey Pointer <robeypointer@gmail.com>
    #
    # This file is part of paramiko.
    #
    # Paramiko is free software; you can redistribute it and/or modify it under the
    # terms of the GNU Lesser General Public License as published by the Free
    # Software Foundation; either version 2.1 of the License, or (at your option)
    # any later version.
    #
    # Paramiko is distributed in the hope that it will be useful, but WITHOUT ANY
    # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
    # A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
    # details.
    #
    # You should have received a copy of the GNU Lesser General Public License
    # along with Paramiko; if not, write to the Free Software Foundation, Inc.,
    # 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA.
    
    
    import socket
    import sys
    from paramiko.py3compat import u
    from models import models
    # from modules.views import log_recording
    import datetime
    import redis
    import time
    
    # windows does not have termios...
    try:
        import termios
        import tty
        has_termios = True
    except ImportError:
        has_termios = False
    
    
    def interactive_shell(chan, user_obj, bind_host_obj, cmd_caches, log_recording):
        '''
        :param chan:
        :param user_obj:
        :param bind_host_obj: 主机
        :param cmd_caches: 命令列表
        :param log_recording: 日志记录
        :return:
        '''
        # 判断是否是windows shell
        if has_termios:
            posix_shell(chan, user_obj, bind_host_obj, cmd_caches, log_recording)
        else:
            windows_shell(chan)
    
    
    def posix_shell(chan, user_obj, bind_host_obj, cmd_caches, log_recording):
        '''
    
        :param chan:
        :param user_obj:
        :param bind_host_obj:
        :param cmd_caches:
        :param log_recording:
        :return:
        '''
        import select
        
        oldtty = termios.tcgetattr(sys.stdin)
        try:
            tty.setraw(sys.stdin.fileno())
            tty.setcbreak(sys.stdin.fileno())
            chan.settimeout(0.0)
            cmd = ''
            tab_key = False
            while True:
                r, w, e = select.select([chan, sys.stdin], [], [])
                if chan in r:
                    try:
                        x = u(chan.recv(1024))
                        if tab_key:
                            if x not in ('x07', '
    '):
                                # print('tab:',x)
                                cmd += x
                            tab_key = False
                        if len(x) == 0:
                            sys.stdout.write('
    *** EOF
    ')
                            # test for redis to mysql
                            break
                        sys.stdout.write(x)
                        sys.stdout.flush()
                    except socket.timeout:
                        pass
                if sys.stdin in r:
                    x = sys.stdin.read(1)
                    if '
    ' != x:
                        cmd += x
                    else:
                        user_record_cmd = user_obj.username + '_user_record'
                        pool = redis.ConnectionPool(host='192.168.84.66', port=6379)
                        user_record = [user_obj.id, bind_host_obj.id, 'cmd', cmd,
                                       time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())]
                        r = redis.Redis(connection_pool=pool)
                        r.lpush(user_record_cmd, user_record)
                        cmd = ''
                        # 最后用户退出的时候取出来log_item 列表循环写入数据库
                    if '	' == x:
                        tab_key = True
                    if len(x) == 0:
                        break
                    chan.send(x)
    
        finally:
            termios.tcsetattr(sys.stdin, termios.TCSADRAIN, oldtty)
    
    
    # thanks to Mike Looijmans for this code
    def windows_shell(chan):
        '''
    
        :param chan:
        :return:
        '''
        import threading
    
        sys.stdout.write("Line-buffered terminal emulation. Press F6 or ^Z to send EOF.
    
    ")
    
        def writeall(sock):
            while True:
                data = sock.recv(256)
                if not data:
                    sys.stdout.write('
    *** EOF ***
    
    ')
                    sys.stdout.flush()
                    break
                sys.stdout.write(data.decode())
                sys.stdout.flush()
            
        writer = threading.Thread(target=writeall, args=(chan,))
        writer.start()
            
        try:
            while True:
                d = sys.stdin.read(1)
                if not d:
                    break
                chan.send(d)
        except EOFError:
            # user hit ^Z or F6
            pass
    View Code

    ssh_login.py

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    # Time:2017/12/17 9:54
    __Author__ = 'Sean Yao'
    import base64
    import getpass
    import os
    import socket
    import sys
    import traceback
    from paramiko.py3compat import input
    from models import models
    import redis
    import datetime
    import time
    
    import paramiko
    try:
        import interactive
    except ImportError:
        from . import interactive
    
    
    def ssh_login(user_obj, bind_host_obj, mysql_engine, log_recording):
        '''
        ssh登陆
        :param user_obj:
        :param bind_host_obj:
        :param mysql_engine: 连接数据库
        :param log_recording: 写日志记录
        :return:
        '''
        # now, connect and use paramiko Client to negotiate SSH2 across the connection
        try:
            client = paramiko.SSHClient()
            client.load_system_host_keys()
            client.set_missing_host_key_policy(paramiko.WarningPolicy())
            print('*** Connecting...')
            client.connect(bind_host_obj.host.ip,
                           bind_host_obj.host.port,
                           bind_host_obj.remote_user.username,
                           bind_host_obj.remote_user.password,
                           timeout=30)
            cmd_caches = []
            chan = client.invoke_shell()
            # print(repr(client.get_transport()))
            print('*** Here we go!
    ')
            # 连接redis
            pool = redis.ConnectionPool(host='192.168.84.66', port=6379)
            # 传一个命令列表给redis
            user_record = [user_obj.id, bind_host_obj.id, 'login',
                           time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())]
            r = redis.Redis(connection_pool=pool)
            # 用用户名做key前缀,避免冲突
            key_name = str(user_obj.username)+'_login'
            r.lpush(key_name, user_record)
            interactive.interactive_shell(chan, user_obj, bind_host_obj, cmd_caches, log_recording)
            chan.close()
            client.close()
            # 数据库写入操作
            login_record = r.lrange(key_name, 0, -1)
            login_redis_record = login_record[0].decode().replace('[', '').replace(']', '').split(',')
            log_item = models.AuditLog(user_id=login_redis_record[0],
                                       bind_host_id=login_redis_record[1],
                                       action_type='login',
                                       cmd='login',
                                       date=login_redis_record[3].replace("'", ''))
            cmd_caches.append(log_item)
            log_recording(user_obj, bind_host_obj, cmd_caches)
            user_record_cmd = user_obj.username+'_user_record'
            cmd_redis_record = r.lrange(user_record_cmd, 0, -1)
            for i in cmd_redis_record:
                cmd_caches = []
                v = i.decode().replace('[', '').replace(']', '').split(',')
                v2 = v[3].replace("'", '')
                # print(v[0], v[1], v[2], v[3], v[4])
                log_item = models.AuditLog(user_id=v[0],
                                           bind_host_id=v[1],
                                           action_type='cmd',
                                           cmd=v2, date=v[4].replace("'", ''))
                cmd_caches.append(log_item)
                log_recording(user_obj, bind_host_obj, cmd_caches)
            # 当退出的时候将redis的值写入到数据库并且清空redis
            logout_caches = []
            logout_caches.append(models.AuditLog(user_id=user_obj.id,
                                                 bind_host_id=bind_host_obj.id,
                                                 action_type='logout',
                                                 cmd='logout',
                                                 date=datetime.datetime.now()))
            log_recording(user_obj, bind_host_obj, logout_caches)
            # 清空keys
            r.delete(key_name)
            r.delete(user_record_cmd)
        except Exception as e:
            print('*** Caught exception: %s: %s' % (e.__class__, e))
            traceback.print_exc()
            try:
                client.close()
            except:
                pass
            sys.exit(1)
    View Code

    utils.py

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    # Time:2017/12/15 21:48
    __Author__ = 'Sean Yao'
    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):
        '''
        :param msg:
        :param quit:
        :return:
        '''
        output = "33[31;1mError: %s33[0m" % msg
        if quit:
            exit(output)
        else:
            print(output)
    
    def yaml_parser(yml_filename):
        '''
        yaml方法load yaml file and return
        :param yml_filename:
        :return:
        '''
        try:
            yaml_file = open(yml_filename, 'r')
            data = yaml.load(yaml_file)
            return data
        except Exception as e:
            print_err(e)
    View Code

    views.py

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    # Time:2017/12/15 21:34
    __Author__ = 'Sean Yao'
    from models import models
    from conf import settings
    from modules.utils import print_err, yaml_parser
    from modules.db_conn import engine, session
    from modules import ssh_login
    from modules import common_filters
    import codecs
    
    def syncdb(argvs):
        '''
        创建表结构方法
        :param argvs:
        :return:
        '''
        print("Syncing DB....")
        engine = models.create_engine(settings.ConnParams, echo=True)
        models.Base.metadata.create_all(engine)  # 创建所有表结构
    
    def create_hosts(argvs):
        '''
        create 主机
        :param argvs:
        :return:
        '''
        if '-f' in argvs:
            # 指定一个文件名否则报错
            hosts_file = argvs[argvs.index("-f") +1]
        else:
            print_err("invalid usage, should be:
    create_hosts -f <the new hosts file>", quit=True)
        source = yaml_parser(hosts_file)  # 传文件回来
        if source:  # 循环字典
            print(source)
            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 堡垒机用户
        :param argvs:
        :return:
        '''
        if '-f' in argvs:
            remoteusers_file = argvs[argvs.index("-f") +1]
        else:
            print_err("invalid usage, should be:
    create_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:
    createusers -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.HostGroup).filter(models.HostGroup.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:
    creategroups -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:
    create_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()
                # 取hostname
                assert host_obj  # 断言,必须存在
                for item in val['remote_users']:  # 判断
                    print(item)
                    assert item.get('auth_type')
                    if item.get('auth_type') == 'ssh-password':  # 判断认证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)  # 获取到关系后添加session
                    # 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
                    # 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("33[32;1mUsername:33[0m").strip()
            if len(username) == 0:
                continue
            password = input("33[32;1mPassword:33[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 = '''33[32;1m
        ------------- Welcome [%s] login TinyServer -------------
        33[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('33[32;1mz.	ungroupped hosts (%s)33[0m' % len(user.bind_hosts))
                for index, group in enumerate(user.host_groups):
                    print('33[32;1m%s.	%s (%s)33[0m' % (index, group.name, len(group.bind_hosts)))
                # 用户输入
                choice = input("[%s]:" % user.username).strip()
                if len(choice) == 0:
                    continue
                # 如果是z 打印未分组机器
                if choice == 'z':
                    print("------ Group: ungroupped hosts ------")
                    for index, bind_host in enumerate(user.bind_hosts):
                        print("  %s.	%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.	%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)
                                    ssh_login.ssh_login(user,  # 传用户,用户组,连上对应的
                                                        user.host_groups[choice].bind_hosts[user_option],
                                                        session, log_recording)
                    else:
                        print("no this option..")
    
    def log_recording(user_obj, bind_host_obj, logs):
        '''
        flush user operations on remote host into DB
        :param user_obj:
        :param bind_host_obj:
        :param logs: list format [logItem1,logItem2,...]
        :return:
        '''
        # print("33[41;1m--logs:33[0m", logs)
        session.add_all(logs)
        session.commit()
    def user_record_cmd(argvs):
        '''
        查看操作记录方法
        :param argvs:
        :return:
        '''
        print('going to start view record')
        user = auth()
        # 默认root可以查所有人的记录
        if user.username == 'root':
            print('welcome %s ' % user.username)
            exit_flag = False
            # 用户对象
            user_obj = session.query(models.Userprofile).filter().all()
            # 循环查看堡垒机用户操作
            while not exit_flag:
                for user_profile_list in user_obj:
                    # 打印堡垒机用户,根据堡垒机用户ID选择其管辖的机器并打印日志
                    print("%s.	%s" % (user_profile_list.id, user_profile_list.username))
                choice = input("[%s]:" % user.username).strip()
                for user_profile_list in user_obj:
                    if str(choice) == str(user_profile_list.id):
                        if user_profile_list.bind_hosts:
                            # 显示未分组的机器
                            print('33[32;1mz.	ungroupped hosts (%s)33[0m' % len(user_profile_list.bind_hosts))
                        else:
                            print(' no binding groups ')
                        for index, group in enumerate(user_profile_list.host_groups):
                            print('33[32;1m%s.	%s (%s)33[0m' % (index, group.name, len(group.bind_hosts)))
                        choice = input("[%s]:" % user.username).strip()
                        if choice.isdigit():  # 打印分组的机器
                            choice = int(choice)
                            if choice < len(user_profile_list.host_groups):
                                print("------ Group: %s ------" % user_profile_list.host_groups[choice].name)
                                for index, bind_host in enumerate(user_profile_list.host_groups[choice].bind_hosts):
                                    print("  %s.	%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_profile_list.host_groups[choice].bind_hosts):
                                            # print('host:', user_profile_list.host_groups[choice].bind_hosts[user_option])
                                            data = 
                                                session.query(models.AuditLog).filter(
                                                    models.AuditLog.user_id == user_profile_list.id,
                                                    models.AuditLog.bind_host_id == user_profile_list.host_groups[choice].
                                                    bind_hosts[user_option].id).all()
                                            if data:
                                                for index, i in enumerate(data):
                                                    # redis 写入value的时候带有了	 
     等需要转义
                                                    # 第一个注释从数据库里读注释的这种不能转移	,
                                                    # 第二个和现行的俩种中文转义有些问题
                                                    # print(i.user_id, i.bind_host_id, i.action_type, i.cmd, i.date)
                                                    # print(i.user_id, i.bind_host_id, i.action_type,
                                                    #        codecs.getdecoder("unicode_escape")(i.cmd)[0], i.date)
                                                    # print(i.user_id, i.bind_host_id, i.action_type,
                                                    #       i.cmd.encode().decode('unicode-escape'), i.date)
                                                    print(index, i.date, i.cmd.encode().decode('unicode-escape'))
                                            else:
                                                print('no record in host:', user_profile_list.host_groups[choice].
                                                      bind_hosts[user_option])
        # 其他人只能查自己的操作记录
        else:
            exit_flag = False
            while not exit_flag:
                if user.bind_hosts:
                    # 显示未分组的机器
                    print('33[32;1mz.	ungroupped hosts (%s)33[0m' % len(user.bind_hosts))
                for index, group in enumerate(user.host_groups):
                    print('33[32;1m%s.	%s (%s)33[0m' % (index, group.name, len(group.bind_hosts)))
                choice1 = input("[%s]:" % user.username).strip()
                # 查询选项
                if choice1 == 'z':
                    print("------ Group: ungroupped hosts ------")
                    for index, bind_host in enumerate(user.bind_hosts):
                        print("  %s.	%s@%s(%s)" % (index,
                                                    bind_host.remote_user.username,
                                                    bind_host.host.hostname,
                                                    bind_host.host.ip,
                                                    ))
                    print("----------- END -----------")
                elif choice1.isdigit():  # 打印分组的机器
                    choice = int(choice1)
                    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.	%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 view record]:").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):
                                    data = session.query(models.AuditLog)
                                        .filter(models.AuditLog.user_id == user.id,
                                                models.AuditLog.bind_host_id == user.host_groups[choice].
                                                bind_hosts[user_option].id).all()
                                    # print(user.host_groups[choice].bind_hosts[user_option].id)
                                    if data:
                                        for index, i in enumerate(data):
                                            print(index, i.date, i.cmd.encode().decode('unicode-escape'))
                                    else:
                                        print('no record in host:', user.host_groups[choice].bind_hosts[user_option])
                    else:
                        print("no this option..")
    View Code

    share

    new_bindhosts.yml

    bind1:
      hostname: server1
      remote_users:
        - user0:
          username: root
          auth_type: ssh-password
          password: 123456
      groups:
        - bj_group
      user_profiles:
        - sean
    
    bind2:
      hostname: server2
      remote_users:
        - user0:
          username: root
          auth_type: ssh-password
          password: 123456
      groups:
        - bj_group
        - sh_group
      user_profiles:
        - sean
        - jack
    
    bind3:
      hostname: server3
      remote_users:
        - user0:
          username: root
          auth_type: ssh-password
          password: 123456
      groups:
        - bj_group
        - sh_group
      user_profiles:
        - sean
        - jack
    
    bind4:
      hostname: server2
      remote_users:
        - user2:
          username: colin
          auth_type: ssh-password
          password: 123@123
      groups:
        - web_servers
      user_profiles:
        - root
    
    bind5:
      hostname: server3
      remote_users:
        - user3:
          username: web
          auth_type: ssh-password
          password: 12345678
        - user1:
          username: mysql
          auth_type: ssh-password
          password: 12345678
      groups:
        - web_servers
        - db_servers
      user_profiles:
        - root
    View Code

    new_groups.yml

    bj_group:
      user_profiles:
        - sean
    
    sh_group:
      user_profiles:
        - jack
    
    db_servers:
      user_profiles:
      - root
    
    web_servers:
      user_profiles:
      - root
    View Code

    new_hosts.yml

    server1:
      ip: 192.168.84.66
      port: 12321
    
    server2:
      ip: 192.168.84.67
      port: 12321
    
    server3:
      ip: 192.168.84.68
      port: 12321
    View Code

    new_remoteusers.yml

    user0:
      auth_type:  ssh-password
      username: root
      password: 123456
    
    user1:
      auth_type:  ssh-password
      username: mysql
      password: 12345678
    
    user2:
      auth_type:  ssh-password
      username: colin
      password: 123@123
    
    user3:
      auth_type:  ssh-password
      username: web
      password: 12345678
    
    user4:
      auth_type:  ssh-key
      username: root
    View Code

    new_user.yml

    root:
      password: 123@456
    
    sean:
      password: 123456
    
    jack:
      password: 123456
    View Code

    程序测试样图:

    1. 创建表和插入表数据

    2. 查看绑定关系

    3. 登陆和写入命令

    4. 不同用户权限,组权限,登陆权限和查看日志权限

  • 相关阅读:
    Web安全防护(二)
    Netty高级应用及聊天室实战
    Bert不完全手册2. Bert不能做NLG?MASS/UNILM/BART
    Bert不完全手册1. Bert推理太慢?模型蒸馏
    第五篇 抖音的强大对手来了,用Flutter手撸一个抖音国际版,看看有多炫
    06Atomic原子操作类详解
    12ForkJoin
    07深入理解synchronized
    11阻塞队列BlockingQueue
    14Future&CompletableFuture
  • 原文地址:https://www.cnblogs.com/sean-yao/p/8064885.html
Copyright © 2020-2023  润新知