作业需求:
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)
表结构设计图:
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 # 堡垒机用户配置文件
创建表和使用方法:
先要创建数据库: 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
程序核心代码:
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)
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 # 查看用户操作命令
}
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"
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")
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("