老男孩Day14作业:堡垒机

时间:2022-05-12 03:26:01

一、作业需求:

1.业务需求

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

2.功能需求

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

 

二、表结构图

老男孩Day14作业:堡垒机

 三、

老男孩Day14作业:堡垒机老男孩Day14作业:堡垒机
一、作业需求:
1.业务需求

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

2.功能需求

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

二、博客地址:https://www.cnblogs.com/catepython/p/9177109.html

三、运行环境

操作系统:Win10

Python:3.6.4rcl

Pycharm:2017.3.4
readme

 

四、程序架构图

老男孩Day14作业:堡垒机

 

五、核心模块

bin目录

老男孩Day14作业:堡垒机老男孩Day14作业:堡垒机
#-*-coding:utf-8 -*-
# Author: D.Gray
import os,sys
BASE_DESC = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
#print(BASE_DESC)
sys.path.append(BASE_DESC)

from modules.actions import excute_from_command_line
excute_from_command_line(sys.argv)
start

conf目录

老男孩Day14作业:堡垒机老男孩Day14作业:堡垒机
#-*-coding:utf-8 -*-
# Author: D.Gray
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  # 查看用户操作命令
}
action_registers
老男孩Day14作业:堡垒机老男孩Day14作业:堡垒机
#-*-coding:utf-8 -*-
# Author: D.Gray
import sqlalchemy
import os,sys
BASE_DESC = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
#print(BASE_DESC)
sys.path.append(BASE_DESC)


CONN = 'mysql+pymysql://root:admin1988@localhost/mychine?charset=utf8'
setting

databases目录

老男孩Day14作业:堡垒机老男孩Day14作业:堡垒机
#-*-coding:utf-8 -*-
# Author: D.Gray
import sqlalchemy
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship
from sqlalchemy_utils import ChoiceType
from sqlalchemy import Column,Integer,String,ForeignKey,UniqueConstraint,Table,Text,DateTime
from conf.setting import CONN

Base = declarative_base()

user_m2m_bind = Table(
    'user_m2m_bind',Base.metadata,
    Column('user_profile_id',Integer,ForeignKey('user_profile.id')),
    Column('bind_host_id',Integer,ForeignKey('bind_host.id'))
)

bind_m2m_group = Table(
    'bind_m2m_group',Base.metadata,
    Column('group_id',Integer,ForeignKey('group.group_id')),
    Column('bind_host_id',Integer,ForeignKey('bind_host.id'))
)

user_m2m_group = Table(
    'user_m2m_group',Base.metadata,
    Column('group_id',Integer,ForeignKey('group.group_id')),
    Column('user_id',Integer,ForeignKey('user_profile.id'))
)

class Host(Base):
    __tablename__ = 'host'
    host_id = Column(Integer,primary_key=True)
    host_name = Column(String(32),unique=True)
    IP = Column(String(32),nullable=False,unique=True)
    port = Column(Integer,default=22)

    def __repr__(self):
        return '<名称:【%s】    IP:【%s】     port:【%s】>'%(self.host_name,self.IP,self.port)

class Group(Base):
    __tablename__ = 'group'
    group_id = Column(Integer,primary_key=True)
    group_name = Column(String(32),nullable=False,unique=True)
    group_bind = relationship('BindHost',secondary = 'bind_m2m_group',backref = 'groups_key')

    def __repr__(self):
        return '<组名:【%s】>'%(self.group_name)

class RemUser(Base):
    '''
    远程登录用户
    '''
    __tablename__ = 'rem_user'
    __table_args__ = (UniqueConstraint('auth_type','username','password',name = 'rems_uc'),)
    id = Column(Integer,primary_key=True)
    Auth_types = [
        ('ssh-password','SSH/Password'),
        ('ssh-key','SSH/Key'),
    ]
    auth_type = Column(ChoiceType(Auth_types))
    username = Column(String(32),nullable=False)
    password = Column(String(32))

    def __repr__(self):
        return '<名称:【%s】    密码:【%s】     验证方式:【%s】>'%(self.username,self.password,self.auth_type)

class BindHost(Base):
    '''
    此表是用来实现操控主机IP 和 登录用户 之间的绑定关系
        IP                                             远程登录名
    192.168.111.128                                       root
    192.168.111.129                                       admin_kyo
    host_id                                               remuser_id
    '''
    __tablename__ = 'bind_host'
    __table_args__ = (UniqueConstraint('remuser_id','host_id',name = 'binds_uc'),)
    id = Column(Integer,primary_key=True)
    remuser_id = Column(Integer,ForeignKey('rem_user.id'),nullable=False)
    host_id = Column(Integer,ForeignKey('host.host_id'),nullable=False)
    bind_hosts = relationship('Host',backref = 'bind_hosts')
    bind_remusers = relationship('RemUser',backref = 'bind_remusers')

    def __repr__(self):
        return '<IP:【%s】    远程登录名:【%s】>'\
               %(self.bind_hosts.IP,self.bind_remusers.username)

class UserProfile(Base):
    '''
    堡垒机用户
    '''
    __tablename__ = 'user_profile'
    id = Column(Integer,primary_key=True)
    user_name = Column(String(32),nullable=False)
    password = Column(String(32),nullable=False)
    profile_bind = relationship('BindHost',secondary = 'user_m2m_bind',backref = 'user_profiles')
    profile_group = relationship('Group',secondary = 'user_m2m_group',backref = 'profile_groups')

    def __repr__(self):
        return '<名称:【%s】    密码:【%s】>'\
               %(self.user_name,self.password)

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 = [
        (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")
create_table

modules目录

老男孩Day14作业:堡垒机老男孩Day14作业:堡垒机
#-*-coding:utf-8 -*-
# Author: D.Gray

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):
    '''
    命令执行函数
    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:])
actions
老男孩Day14作业:堡垒机老男孩Day14作业:堡垒机
#-*-coding:utf-8 -*-
# Author: D.Gray
from database import create_table
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(create_table.BindHost).\
        filter(create_table.Host.host_name.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(create_table.UserProfile).filter(create_table.UserProfile.user_name.
                                                                   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
common_filters
老男孩Day14作业:堡垒机老男孩Day14作业:堡垒机
#-*-coding:utf-8 -*-
# Author: D.Gray
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from conf import setting

engine = create_engine(setting.CONN)
# 创建与数据库的会话session class ,注意,这里返回给session的是个class,不是实例
SessionCls = sessionmaker(bind=engine)
session = SessionCls()
db_conn
老男孩Day14作业:堡垒机老男孩Day14作业:堡垒机
#-*-coding:utf-8 -*-
# Author: D.Gray
# 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 database import create_table
# 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', '\r\n'):
                            # print('tab:',x)
                            cmd += x
                        tab_key = False
                    if len(x) == 0:
                        sys.stdout.write('\r\n*** EOF\r\n')
                        # 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 '\r' != x:
                    cmd += x
                else:
                    user_record_cmd = user_obj.username + '_user_record'
                    pool = redis.ConnectionPool(host='localhost', port=22)
                    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 '\t' == 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.\r\n\r\n")

    def writeall(sock):
        while True:
            data = sock.recv(256)
            if not data:
                sys.stdout.write('\r\n*** EOF ***\r\n\r\n')
                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
interactive.py
老男孩Day14作业:堡垒机老男孩Day14作业:堡垒机
#-*-coding:utf-8 -*-
# Author: D.Gray
import base64
import getpass
import os
import socket
import sys
import traceback
from paramiko.py3compat import input
from database import create_table
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!\n')
        # 连接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 = create_table.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 = create_table.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(create_table.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)
ssh_login
老男孩Day14作业:堡垒机老男孩Day14作业:堡垒机
#-*-coding:utf-8 -*-
# Author: D.Gray
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 = "\033[31;1mError: %s\033[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)
utils
老男孩Day14作业:堡垒机老男孩Day14作业:堡垒机
#-*-coding:utf-8 -*-
# Author: D.Gray
from database import create_table
from pymysql.err import IntegrityError
from conf import setting
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,os

def syncdb(argvs):
    '''
    创建表结构方法
    :param argvs:
    :return:
    '''
    print("Syncing DB....")
    engine = create_table.create_engine(setting.CONN, echo=True)
    create_table.Base.metadata.create_all(engine)  # 创建所有表结构


def create_hosts(argvs):
    '''
    create 主机
    :param argvs:
    :return:
    '''
    if '-f' in argvs:
        # 指定一个文件名否则报错
        hosts_file = argvs[argvs.index("-f") +1]
        host_path = os.path.join(setting.BASE_DESC,hosts_file)
        #print('hosts_path:',host_path)
    else:
        print_err("invalid usage, should be:\ncreate_hosts -f <the new hosts file>", quit=True)
    source = yaml_parser(host_path)  # 传文件回来
    if source:  # 循环字典
        print(source)
        for key, val in source.items():
            print(key, val)
            obj = create_table.Host(host_name=key, IP=val.get('ip'), port=val.get('port') or 22)
            # 添加到表
            try:
                session.add(obj)
            except IntegrityError as e:
                print('主机名和主机IP是唯一值已在数据库创建:',e)
            else:
                session.commit()

def create_remoteusers(argvs):
    '''
    create 远程用户数据
    :param argvs:
    :return:
    '''
    if '-f' in argvs:
        remoteusers_file = argvs[argvs.index("-f") +1]
        host_path = os.path.join(setting.BASE_DESC, remoteusers_file)
    else:
        print_err("invalid usage, should be:\ncreate_remoteusers -f <the new remoteusers file>", quit=True)
    source = yaml_parser(host_path)
    if source:
        for key, val in source.items():
            print(key, val)
            obj = create_table.RemUser(username=val.get('username'), auth_type=val.get('auth_type'),
                                    password=val.get('password'))
            session.add(obj)
        session.commit()

def create_users(argvs):
    '''
    create 堡垒机用户数据
    create little_finger access user
    :param argvs:
    :return:
    '''
    if '-f' in argvs:
        user_file = argvs[argvs.index("-f") +1 ]
        host_path = os.path.join(setting.BASE_DESC, user_file)
    else:
        print_err("invalid usage, should be:\ncreateusers -f <the new users file>",quit=True)

    source = yaml_parser(host_path)
    if source:
        for key, val in source.items():
            print(key, val)
            obj = create_table.UserProfile(user_name=key, password=val.get('password'))
            if val.get('groups'):
                groups = session.query(create_table.Group).\
                    filter(create_table.Group.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 组数据
    create groups
    :param argvs:
    :return:
    '''
    if '-f' in argvs:
        group_file = argvs[argvs.index("-f") + 1]
        host_path = os.path.join(setting.BASE_DESC, group_file)
    else:
        print_err("invalid usage, should be:\ncreategroups -f <the new groups file>", quit=True)
    source = yaml_parser(host_path)
    if source:
        for key, val in source.items():
            print(key, val)
            obj = create_table.Group(group_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 IP和远程用户关联数据
    create bind hosts
    :param argvs:
    :return:
    '''
    if '-f' in argvs:
        bindhosts_file = argvs[argvs.index("-f") + 1]
        host_path = os.path.join(setting.BASE_DESC, bindhosts_file)
    else:
        print_err("invalid usage, should be:\ncreate_hosts -f <the new bindhosts file>",quit=True)
    source = yaml_parser(host_path)
    if source:
        for key, val in source.items():
            print(key, val)
            # 获取到了主机
            host_obj = session.query(create_table.Host).\
                filter(create_table.Host.host_name == val.get('host_name')).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(create_table.RemUser).filter(
                        create_table.RemUser.username == item.get('username'),
                        create_table.RemUser.password == item.get('password')
                                                    ).first()
                else:
                    # 获取远程用户
                    remoteuser_obj = session.query(create_table.RemUser).filter(
                        create_table.RemUser.username == item.get('username'),
                        create_table.RemUser.auth_type == item.get('auth_type'),
                                                    ).first()
                if not remoteuser_obj:  # 没取到,程序退出
                    print_err("RemoteUser obj %s does not exist." % item, quit=True)
                bindhost_obj = create_table.BindHost(host_id=host_obj.host_id, remuser_id=remoteuser_obj.id)
                session.add(bindhost_obj)  # 获取到关系后添加session
                # for groups this host binds to
                if source[key].get('groups'):  # 获取组
                    group_objs = session.query(create_table.Group).filter(create_table.Group.group_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(create_table.UserProfile).\
                        filter(create_table.UserProfile.user_name.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(create_table.UserProfile).filter(create_table.UserProfile.user_name == username,
                                                                  create_table.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):
    '''
    :param user: 接收start_session函数的user
    :return:
    '''
    WELCOME_MSG = '''\033[32;1m
    ------------- Welcome [%s] login TinyServer -------------
    \033[0m''' % user.user_name
    print(WELCOME_MSG)

def start_session(argvs):
    '''
    开始远程登陆函数
    :param argvs:
    :return:
    '''
    print('going to start sesssion ')
    user = auth()               #调用auth认证函数 来判断输入的堡垒机用户是否存在
    if user:
        welcome_msg(user)
        # print(user.bind_hosts)
        # print(user.host_groups)
        exit_flag = False
        while not exit_flag:
            if user.profile_bind:
                # 显示未分组的机器
                print('\033[32;1mz.\t z查看未分组主机列表/任意键查看已分组主机列表 (%s)\033[0m' % len(user.profile_bind))
            for index, group in enumerate(user.profile_group):
                print('\033[32;1m%s.\t%s (%s)\033[0m' % (index, group.group_name, len(group.group_bind)))
            # 用户输入
            choice = input("[%s]:" % user.user_name).strip()
            if len(choice) == 0:
                continue
            # 如果是z 打印未分组机器
            if choice == 'z':
                print("------ Group: 未分组主机 ------")
                for index, bind_host in enumerate(user.profile_bind):
                    print("  %s.\t%s@%s(%s)" % (index,
                                                bind_host.bind_remusers.username,
                                                bind_host.bind_hosts.host_name,
                                                bind_host.bind_hosts.IP,
                                                ))
                print("----------- END -----------")
            elif choice.isdigit():  # 打印分组的机器
                choice = int(choice)
                if choice < len(user.profile_group):
                    print("------ Group: %s ------" % user.profile_group[choice].group_name)
                    for index, bind_host in enumerate(user.profile_group[choice].group_bind):
                        print("  %s.\t%s@%s(%s)" % (index,
                                                    bind_host.bind_remusers.username,
                                                    bind_host.bind_hosts.host_name,
                                                    bind_host.bind_hosts.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("\033[41;1m--logs:\033[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.user_name == 'root':
        print('welcome 【%s】 ' % user.user_name)
        exit_flag = False
        # 用户对象
        user_obj = session.query(create_table.UserProfile).filter().all()
        # 循环查看堡垒机用户操作
        while not exit_flag:
            for user_profile_list in user_obj:
                # 打印堡垒机用户,根据堡垒机用户ID选择其管辖的机器并打印日志
                print("%s.\t%s" % (user_profile_list.id, user_profile_list.user_name))
            choice = input("[%s]:" % user.user_name).strip()
            for user_profile_list in user_obj:
                if str(choice) == str(user_profile_list.id):
                    if user_profile_list.profile_bind:
                        # 显示未分组的机器
                        print('\033[32;1mz.\tungroupped hosts (%s)\033[0m' % len(user_profile_list.profile_bind))
                    else:
                        print(' no binding groups ')
                    for index, group in enumerate(user_profile_list.profile_group):
                        print('\033[32;1m%s.\t%s (%s)\033[0m' % (index, group.group_name, len(group.group_bind)))
                    choice = input("[%s]:" % user.user_name).strip()
                    if choice.isdigit():  # 打印分组的机器
                        choice = int(choice)
                        if choice < len(user_profile_list.profile_group):
                            print("------ Group: %s ------" % user_profile_list.profile_group[choice].group_name)
                            for index, bind_host in enumerate(user_profile_list.profile_group[choice].group_bind):
                                print("  %s.\t%s@%s(%s)" % (index,
                                                            bind_host.remote_user.user_name,
                                                            bind_host.host.host_name,
                                                            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.profile_group[choice].bind_hosts):
                                        # print('host:', user_profile_list.host_groups[choice].bind_hosts[user_option])
                                        data = \
                                            session.query(create_table.AuditLog).filter(
                                                create_table.AuditLog.user_id == user_profile_list.id,
                                                create_table.AuditLog.bind_host_id ==
                                                user_profile_list.profile_group[choice].
                                                group_bind[user_option].id).all()
                                        if data:
                                            for index, i in enumerate(data):
                                                # redis 写入value的时候带有了\t \n 等需要转义
                                                # 第一个注释从数据库里读注释的这种不能转移\t,
                                                # 第二个和现行的俩种中文转义有些问题
                                                # 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.profile_group[choice].
                                                  group_bind[user_option])
    # 其他人只能查自己的操作记录
    else:
        exit_flag = False
        while not exit_flag:
            if user.profile_bind:
                # 显示未分组的机器
                print('\033[32;1mz.\tungroupped hosts (%s)\033[0m' % len(user.profile_bind))
            for index, group in enumerate(user.profile_group):
                print('\033[32;1m%s.\t%s (%s)\033[0m' % (index, group.group_name, len(group.group_bind)))
            choice1 = input("[%s]:" % user.user_name).strip()
            # 查询选项
            if choice1 == 'z':
                print("------ Group: ungroupped hosts ------")
                for index, bind_host in enumerate(user.profile_bind):
                    print("  %s.\t%s@%s(%s)" % (index,
                                                bind_host.remote_user.user_name,
                                                bind_host.host.host_name,
                                                bind_host.host.IP,
                                                ))
                print("----------- END -----------")
            elif choice1.isdigit():  # 打印分组的机器
                choice = int(choice1)
                if choice < len(user.host_groups):
                    print("------ Group: %s ------" % user.profile_group[choice].name)
                    for index, bind_host in enumerate(user.profile_group[choice].group_bind):
                        print("  %s.\t%s@%s(%s)" % (index,
                                                    bind_host.remote_user.user_name,
                                                    bind_host.host.host_name,
                                                    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.profile_group[choice].group_bind):
                                data = session.query(create_table.AuditLog)\
                                    .filter(create_table.AuditLog.user_id == user.id,
                                            create_table.AuditLog.bind_host_id == user.profile_group[choice].
                                            group_bind[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.profile_group[choice].group_bind[user_option])
                else:
                    print("no this option..")
views

share目录

老男孩Day14作业:堡垒机老男孩Day14作业:堡垒机
bind1:
  host_name: server1
  remote_users:
    - user0:
      username: root
      auth_type: ssh-password
      password: 123456
  groups:
    - bj_group
  user_profiles:
    - sean

bind2:
  host_name: server2
  remote_users:
    - user0:
      username: root
      auth_type: ssh-password
      password: 123456
  groups:
    - bj_group
    - sh_group
  user_profiles:
    - sean
    - jack

bind3:
  host_name: server3
  remote_users:
    - user0:
      username: root
      auth_type: ssh-password
      password: 123456
  groups:
    - bj_group
    - sh_group
  user_profiles:
    - sean
    - jack

bind4:
  host_name: server2
  remote_users:
    - user2:
      username: colin
      auth_type: ssh-password
      password: 123@123
  groups:
    - web_servers
  user_profiles:
    - root

bind5:
  host_name: 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
new_bindhost.yml
老男孩Day14作业:堡垒机老男孩Day14作业:堡垒机
bj_group:
  user_profiles:
    - sean

sh_group:
  user_profiles:
    - jack

db_servers:
  user_profiles:
  - root

web_servers:
  user_profiles:
  - root
new_groups.yml
老男孩Day14作业:堡垒机老男孩Day14作业:堡垒机
server1:
  ip: 192.168.111.128
  port: 22

server2:
  ip: 192.168.111.129
  port: 22

server3:
  ip: 192.168.111.130
  port: 22
new_hosts
老男孩Day14作业:堡垒机老男孩Day14作业:堡垒机
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
new_remoteusers.yml
老男孩Day14作业:堡垒机老男孩Day14作业:堡垒机
root:
  password: 123@456

sean:
  password: 123456

jack:
  password: 123456
new_user.yml

 

六、测试路程图

老男孩Day14作业:堡垒机

老男孩Day14作业:堡垒机

老男孩Day14作业:堡垒机

老男孩Day14作业:堡垒机

老男孩Day14作业:堡垒机

老男孩Day14作业:堡垒机

老男孩Day14作业:堡垒机