作业需求:

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('\r\n*** EOF\r\n')
                        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 == "\r":
                    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("\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:])
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', '\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='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 '\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
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!\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 = 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 = "\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)
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:\ncreate_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:\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.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:\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()
            # 取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("\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 TinyServer -------------
    \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))
            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)))
            # 用户输入
            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.\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)
                                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.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.\t%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('\033[32;1mz.\tungroupped hosts (%s)\033[0m' % len(user_profile_list.bind_hosts))
                    else:
                        print(' no binding groups ')
                    for index, group in enumerate(user_profile_list.host_groups):
                        print('\033[32;1m%s.\t%s (%s)\033[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.\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_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的时候带有了\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.host_groups[choice].
                                                  bind_hosts[user_option])
    # 其他人只能查自己的操作记录
    else:
        exit_flag = False
        while not exit_flag:
            if user.bind_hosts:
                # 显示未分组的机器
                print('\033[32;1mz.\tungroupped hosts (%s)\033[0m' % 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)))
            choice1 = input("[%s]:" % user.username).strip()
            # 查询选项
            if choice1 == '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 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.\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 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. 不同用户权限,组权限,登陆权限和查看日志权限

 

posted on 2017-12-19 12:13  Sean_Yao  阅读(922)  评论(0编辑  收藏  举报