zoukankan      html  css  js  c++  java
  • (转)用Python写堡垒机项目

    原文:https://blog.csdn.net/ywq935/article/details/78816860

    前言
    堡垒机是一种运维安全审计系统。主要的功能是对运维人员的运维操作进行审计和权限控制,风险规避。同时堡垒机还有账号集中管理,单点登陆的功能。

    堡垒机有以下两个至关重要的功能:
    集中管理
    安全审计

    当公司的服务器变的越来越多后,需要操作这些服务器的人就肯定不只是一个运维人员,同时也可能包括多个开发人员,那么这么多的人操作业务系统,如果权限分配不当就会存在很大的安全风险,举几个场景例子:
    设想你们公司有300台Linux服务器,A开发人员需要登录其中5台WEB服务器查看日志或进行问题追踪等事务,同时对另外10台hadoop服务器有root权限,在有300台服务器规模的网络中,按常理来讲你是已经使用了ldap权限统一认证的,你如何使这个开发人员只能以普通用户的身份登录5台web服务器,并且同时允许他以管理员的身份登录另外10台hadoop服务器呢?并且同时他对其它剩下的200多台服务器没有访问权限

    小型公司的运维团队为了方面,整个运维团队的运维人员还是共享同一套root密码,这样内部信任机制虽然使大家的工作方便了,但同时存在着极大的安全隐患,很多情况下,一个运维人员只需要管理固定数量的服务器,毕竟公司分为不同的业务线,不同的运维人员管理的业务线也不同,但如果共享一套root密码,其实就等于无限放大了每个运维人员的权限,也就是说,如果某个运维人员想干坏事的话,后果很严重。为了降低风险,于是有人想到,把不同业务线的root密码改掉就ok了么,也就是每个业务线的运维人员只知道自己的密码,这当然是最简单有效的方式,但问题是如果同时用了ldap,这样做又比较麻烦,即使设置了root不通过ldap认证,那新问题就是,每次有运维人员离职,他所在的业务线的密码都需要重新改一次。

    因此,堡垒机的诞生就是为了规避这些高风险的问题,同时减少繁琐的重复性工作。

    工作流程图:


    一、需求分析
    1.所有生产服务器配置iptables安全策略,只能通过堡垒机来登陆,用户首先登陆进堡垒机,再通过堡垒机跳转登陆target host.
    2.各IT组,按职能划分一个统一的可以真实登陆target host的账户
    3.组内的成员,使用自己的账号登陆堡垒机,再使用小组账号登陆target host.
    4.审计。记录下各人员的操作记录,出现问题时可以溯源

    二、表结构设计
    本着最少的字段数据冗余的原则,设计了如下几张表,如果各路大神有更好的设计思路,请指点一二~


    表创建代码:

    import os,sys
    BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    sys.path.append(BASE_DIR)
    from sqlalchemy import Table, Column, Enum,Integer,String, ForeignKey,UniqueConstraint
    from sqlalchemy.orm import relationship
    from sqlalchemy.ext.declarative import declarative_base
    from sqlalchemy import create_engine
    from conf import config


    Base = declarative_base()

    user_m2m_group_bind_host = Table('user_m2m_group_bind_host', Base.metadata,
    Column('id',Integer,autoincrement=True,primary_key=True),
    Column('user_id', Integer, ForeignKey('user.id')),
    Column('group_bind_host_id', Integer, ForeignKey('group_bind_host.id')),
    )

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


    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)
    groups=relationship('Group',secondary='group_bind_host')

    def __repr__(self):
    return self.hostname

    class Group(Base):
    __tablename__ = 'group'
    id = Column(Integer, primary_key=True)
    name = Column(String(64), unique=True)
    login_passwd=Column(String(64))
    bind_hosts = relationship("Host",secondary='group_bind_host')
    users=relationship('User',secondary='user_m2m_group')

    def __repr__(self):
    return self.name


    class Group_Bind_Host(Base):
    __tablename__ = "group_bind_host"
    __table_args__ = (UniqueConstraint('group_id','host_id', name='_host_remoteuser_uc'),)
    id = Column(Integer, primary_key=True)
    group_id = Column(Integer, ForeignKey('group.id'))
    host_id = Column(Integer,ForeignKey('host.id'))

    users=relationship('User',secondary='user_m2m_group_bind_host',backref='group_bind_hosts')

    #host = relationship("Host")
    #host_group = relationship("Group",backref="bind_hosts")
    #group = relationship("Group")


    class User(Base):
    __tablename__ = 'user'
    id = Column(Integer,autoincrement=True,primary_key=True)
    username = Column(String(32))
    password = Column(String(128))
    groups = relationship("Group",secondary="user_m2m_group")
    bind_hosts = relationship("Group_Bind_Host", secondary='user_m2m_group_bind_host')

    def __repr__(self):
    return self.username


    class Log_audit(Base):
    __tablename__= 'log_audit'
    id = Column(Integer,autoincrement=True,primary_key=True)
    user_id = Column(Integer)
    user_name = Column(String(32))
    host_ip = Column(String(32))
    login_user = Column(String(32))
    action_type = Column(String(16))
    cmd=Column(String(128))
    date = Column(String(16))

    if __name__ == "__main__":
    engine=create_engine(config.engine_param)
    Base.metadata.create_all(engine) # 创建表结构
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    三、项目代码
    1.整体结构:


    2.功能说明:
    1.管理功能
    ——表结构初始化创建
    ——添加组
    ——添加主机
    ——添加用户
    ——添加组-主机绑定

    doc目录下提供了几个添加元素的example文档,使用yaml模块解析这些文档,解析为dict数据类型,将解析出的数据添加进数据库内,例如:

    对应实现添加user功能的函数:


    2.用户视图
    ——查看属组
    —查看属组有权限登陆的主机
    ——直接输入IP登陆主机
    —查看该IP主机是否有可用的有权限的账户可供登陆
    ——开始会话连接,执行命令时向记录审计日志表添加item

    ssh会话实现:
    使用了paramiko的demo模块,这个模块本身的交互功能是使用select模型来实现的,使用select监听会话句柄、sys.stdin(标准输入)的可读可写状态,实现字符在终端界面的输入输出。关于I/O多路复用几种模型的个人解读,可以翻看此前的博客:网络编程之I/O模型(以吃牛肉面为例)。
    对paramiko交互模块进行修改添加记录功能:当标准输入触发回车键时(代表一个命令输入完毕开始执行),记录下执行人、登陆用户、时间、命令内容,写入数据库。修改后的交互模块代码如下:

    # 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
    import datetime
    from moudle import table_init
    from moudle.db_conn import session

    # windows does not have termios...
    try:
    import termios
    import tty
    has_termios = True
    except ImportError:
    has_termios = False


    def interactive_shell(chan,user_obj,choose_host,choose_group):
    if has_termios:
    posix_shell(chan,user_obj,choose_host,choose_group)
    else:
    windows_shell(chan)


    def posix_shell(chan,user_obj,choose_host,choose_group):
    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 (' ',' '):
    #print('tab:',x)
    cmd += x
    tab_key = False
    if len(x) == 0:
    sys.stdout.write(' *** EOF ')
    break
    sys.stdout.write(x)
    sys.stdout.flush()
    except socket.timeout:
    pass

    if sys.stdin in r:
    x = sys.stdin.read(1)
    if ' ' != x:
    cmd +=x #输入字符不包含回车,则命令还未输入完成,包含回车且输入字符长度大于0,则记录日志
    if ' ' == x and len(cmd)>0:
    log_item = table_init.Log_audit(user_id=user_obj.id,
    user_name=user_obj.username,
    host_ip=choose_host.ip,
    login_user=choose_group.name,
    action_type='cmd',
    cmd=cmd ,
    date=datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")

    )
    session.add(log_item)
    session.commit()
    cmd = ''

    if ' ' == x:
    tab_key = True
    # if len(x) == 0:
    # break
    chan.send(x)

    finally:
    termios.tcsetattr(sys.stdin, termios.TCSADRAIN, oldtty)


    # thanks to Mike Looijmans for this code
    def windows_shell(chan):
    import threading

    sys.stdout.write("Line-buffered terminal emulation. Press F6 or ^Z to send EOF. ")

    def writeall(sock):
    while True:
    data = sock.recv(256)
    if not data:
    sys.stdout.write(' *** EOF *** ')
    sys.stdout.flush()
    break
    sys.stdout.write(data)
    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
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    四、运行效果:
    在堡垒机上添加用户,在目标主机上添加一个对应登陆用户:
    在堡垒机上该用户环境变量配置文件中加入:

    /usr/local/python3/bin/python3 /usr/local/packages/MyFortress/bin/user_interface.py
    1
    使其打开shell后自动运行堡垒机程序,效果如下:


    查看数据库审计日志表,记录正常:


    写了一个星期,终于能跑起来了,github链接:
    https://github.com/yinwenqin/MyFortress-Server
    ---------------------
    作者:ywq935
    来源:CSDN
    原文:https://blog.csdn.net/ywq935/article/details/78816860?utm_source=copy
    版权声明:本文为博主原创文章,转载请附上博文链接!

  • 相关阅读:
    fatal: unable to access 'https://github.com/github-eliviate/papers.git/': Failed to connect to github.com port 443 after 21107 ms: Timed out
    ImportError: attempted relative import with no known parent package
    python 创建中文目录
    pyodbc.InterfaceError: ('IM002', '[IM002] [Microsoft][ODBC 驱动程序管理器] 未发现数据源名称并且未指定默认驱动程序 (0) (SQLDriverConnect)')
    pyodbc.InterfaceError: ('28000', '[28000] 用户 'sa' 登录失败。 (18456) 无法打开登录所请求的数据库 "ARCHIVEDB"。登录失败。
    pyodbc.OperationalError: ('08001', '[08001] 无法打开与 SQL Server 的连接[08001]登录超时已过期 (0); 与 SQL Server 建立连接时发生了与网络相关的或特定于实例的错误。找不到或无法访问服务器。请检查实例名称是否正确以及 SQL Server 是否配置为允许远程连接。
    医学图像分割论文:Swin-Unet—Unet-like Pure Transformer for Medical Image Segmentation_202105.05537
    网络请求例子
    blog.devtang.com
    nsdata
  • 原文地址:https://www.cnblogs.com/liujiacai/p/9792107.html
Copyright © 2011-2022 走看看