作业需求:
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)
表结构设计图:
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("\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:])
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
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()
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
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)
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)
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..")
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
new_groups.yml
bj_group:
user_profiles:
- sean
sh_group:
user_profiles:
- jack
db_servers:
user_profiles:
- root
web_servers:
user_profiles:
- root
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
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
new_user.yml
root:
password: 123@456
sean:
password: 123456
jack:
password: 123456
程序测试样图:
1. 创建表和插入表数据
2. 查看绑定关系
3. 登陆和写入命令
4. 不同用户权限,组权限,登陆权限和查看日志权限