编程爱好者联盟 2017-03-16
前言: 好几天没写博客了哈,这篇博客主要介绍堡垒机的功能与作用。之前没听过堡垒机的可以看看,还较详细地讲了数据库表结构的设计,写完这篇博客,感觉数据库真是博大精深……本来还想把这个项目做完的……but ...待更新吧……
到目前为止,很多公司对堡垒机依然不太感冒,其实是没有充分认识到堡垒机在IT管理中的重要作用的,很多人觉得,堡垒机就是跳板机,其实这个认识是不全面的,跳板功能只是堡垒机所具备的功能属性中的其中一项而已,下面我就给大家介绍一下堡垒机的重要性,以帮助大家参考自己公司的业务是否需要部署堡垒机。堡垒机有以下两个至关重要的功能:
当你公司的服务器变的越来越多后,需要操作这些服务器的人就肯定不只是一个运维人员,同时也可能包括多个开发人员,那么这么多的人操作业务系统,如果权限分配不当就会存在很大的安全风险,举几个场景例子:
其实上面的问题,我觉得可以很简单的通过堡垒机来实现,收回所有人员的直接登录服务器的权限,所有的登录动作都通过堡垒机授权,运维人员或开发人员不知道远程服务器的密码,这些远程机器的用户信息都绑定在了堡垒机上,堡垒机用户只能看到他能用什么权限访问哪些远程服务器。
在回收了运维或开发人员直接登录远程服务器的权限后,其实就等于你们公司生产系统的所有认证过程都通过堡垒机来完成了,堡垒机等于成了你们生产系统的SSO(single sign on)模块了。你只需要在堡垒机上添加几条规则就能实现以下权限控制了:
审计管理其实很简单,就是把用户的所有操作都纪录下来,以备日后的审计或者事故后的追责。在纪录用户操作的过程中有一个问题要注意,就是这个纪录对于操作用户来讲是不可见的,什么意思?就是指,无论用户愿不愿意,他的操作都会被纪录下来,并且,他自己如果不想操作被纪录下来,或想删除已纪录的内容,这些都是他做不到的,这就要求操作日志对用户来讲是不可见和不可访问的,通过堡垒机就可以很好的实现。
堡垒机的主要作用权限控制和用户行为审计,堡垒机就像一个城堡的大门,城堡里的所有建筑就是你不同的业务系统 , 每个想进入城堡的人都必须经过城堡大门并经过大门守卫的授权,每个进入城堡的人必须且只能严格按守卫的分配进入指定的建筑,且每个建筑物还有自己的权限访问控制,不同级别的人可以到建筑物里不同楼层的访问级别也是不一样的。还有就是,每个进入城堡的人的所有行为和足迹都会被严格的监控和纪录下来,一旦发生犯罪事件,城堡管理人员就可以通过这些监控纪录来追踪责任人。
防火墙:
配置acl(安全控制):只能从外进入堡垒机,看到主机列表,选择后可进入。
堡垒要想成功完全起到他的作用,只靠堡垒机本身是不够的, 还需要一系列安全上对用户进行限制的配合,堡垒机部署上后,同时要确保你的网络达到以下条件:
业务需求:
功能需求:
看到上面的需求后,我彻底懵比了 )/_\(
看到我上面写那么多文字,不知你看懂没,反正我刚开始没看懂,后来研究下表结构后,再来看,感觉意会到了不少……注意我上面加红色的字……
所有涉及mysql等数据库的产品,都必须先设计好表结构。怎么设计呢? 先大体看下面这个图:
主机表Host:
id | hostname | ip_addr | port |
组表Group:
id | name |
用户表UserInfor:
id | usrename | password |
主机帐户表HostUser: egA主机下多个帐户(eg:mysql,root...)
id | username | password | host_id |
日志表AuditLog:
id | user_id | hostuser_id | action_type | contain | date |
主机 | 主机帐户 | 密码 |
以第一条为例,代表A主机下有个root帐户,该帐户密码为123。
若此时再增入一条记录(第4条数据):
主机 | 主机帐户 | 密码 |
此时问题来了,第3条数据说: B主机下有个mysql帐户,密码为123;而第4条数据说: A主机下有个root帐户,密码为aaa。此时我懵比了,我到底该听谁的?? 实际上A主机上不可能有两个root用户!!
解决方法:
对主机(host_id)与主机帐户(username)进行联合唯一。下面的代码是放在HostUser表结构中: 需要导入UniqueConstraint,重点看下第4,18行代码:
class HostUser(Base): #机器帐户(eg:mysql,root...)信息表 __tablename__ = "host_user" id = Column(Integer,primary_key=True) host_id = Column(Integer, ForeignKey("host.id")) #关联外键主机(A主机下的mysql帐户...) #登陆方式有两种,通过密码,或通过KEY AuthTypes = [ (u'ssh-passwd',u'SSH/Password'), (u'ssh-key',u'SSH/KEY'), ] username = Column(String(64), unique=True, nullable=False) password = Column(String(255)) #255最长了,先做明文密码,后面改进再做密文,password可为空,当选KEY时 #HostUser通过groups可查看组的信息,反之通过hostuser_list可查看组对应主机帐户的信息 groups = relationship("Group", #关联Group表 secondary = UserInfor2HostUser, #关联第三方表 backref = "hostuser_list") #双向关联,不用在Group类中再加这句代码 #联合唯一, 约束(host_id, username)是唯一的;name是自己取的名 __table_args__ = (UniqueConstraint("host_id", "username", name="_host_username_uc"),) def __repr__(self): return "<id=%s, username=%s>" % (self.id, self.username)
因为之前做过主机管理工具,所以对于主机与组,我立马就想到主机与组进行关联。主机B可以属于多个组g1,g2; 一个组g1可以对应多个主机A,B; GOOD! 多对多关联呗。
主机名 | 组名 |
打脸时该到了,B机器有3个帐户(eg: root,mysql,ngnix), 此时代表只有有g1组的访问权限就拥有B机器下所有帐户的访问权限。这属于没仔细分析需求,得把代码删掉重新写过……
需求是最最重要的! 前面有这样一条需求: 可以对设置进行分组,允许用户访问某组机器,但对组里的不同机器依然有不同的访问权限。
解决方法:
A.root | g1 |
根据上表,A机器的root权限分配给g1; 用户若有g1的访问权限,就有A的root, B的ngnix权限。
注意: 可看前面的第4行代码,主机帐户有外键关联主机。
host_id = Column(Integer, ForeignKey("host.id")) #关联外键主机(A主机下的mysql帐户...)
上表是主机帐户表与组表的关联,属于多对多关联,因为没用到django的orm,我是用sqlalchemy,所以需要自己生成中间表HostUser2Group. 至此,组与主机的权限分配OK.
这个点挺简单的啦。
g1 | u1 |
用户u1有g1,g2组的访问权限,用户u2有g1的访问权限。g1对应u1,u2; u1对应g1,g2。显然是多对多关联啦。我定义中间表UserInfor2Group。
需求场景:
现在一个组有100台主机,其中一台主机A的oracle出现了不可言传的损坏。公司是中移动这样的大公司,此时急需oracle厂商的技术支持,现在打电话给oracle的技术人员O,O当然需要登陆主机A进行修复查看啦。按我之前的实现,此时堡垒机的管理者要先为O分配一个组,组包含该损坏的主机A。这这这,太麻烦了,而且O一修复完,堡垒机的管理者还需要将刚刚为O分配的组删除。
解决方法:
将特定的用户与主机帐户表直接进行关联,不用再通过组。
B.oracle | u1 |
显然,这是多对多关联~~ 我定义了中间表UserInfor2HostUser
关于日志表的需求有: 所有的用户操作日志要保留在数据库中。
#日志表 class AuditLog(Base): __tablename__ = 'audit_log' id = Column(Integer, primary_key=True) user_id = Column(Integer, ForeignKey('user_infor.id')) #关联主机帐户表 hostuser_id = Column(Integer, ForeignKey('host_user.id')) """ action_choices = [ (0, 'CMD'), (1, 'Login'), (2, 'Logout'), (3, 'GetFile'), (4, 'SendFile'), (5, 'Exception'), ] """ action_choices2 = [ (u'cmd', u'CMD'), #cmd是存到数据库中的,CMD是显示给用户看的 (u'login', u'Login'), (u'logout', u'Logout'), # (3,'GetFile'), # (4,'SendFile'), # (5,'Exception'), ] #ChoiceType是第三方插件 action_type = Column(ChoiceType(action_choices2)) # action_type = Column(String(64)) contain = Column(String(255)) #包含命令cmd,登陆login,logout... date = Column(DateTime) #关联用户表 user_infor = relationship("UserInfor")
日志表,肯定要记录是哪个用户操作的吧! 因此外键关联用户的id
user_id = Column(Integer, ForeignKey('user_infor.id'))
你肯定要知道用户是在哪台机器上操作的吧,单单知道在哪台机器操作是不够的,还要知道在该机器下哪个帐户(eg:root,mysql...), 因此需要外键关联主机帐户表。
hostuser_id = Column(Integer, ForeignKey('host_user.id'))
表结构总代码:
""" 表结构 """ from sqlalchemy import create_engine, Table from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, String,ForeignKey,UniqueConstraint,\ DateTime from sqlalchemy.orm import sessionmaker, relationship from sqlalchemy_utils import ChoiceType Base = declarative_base() # 生成一个SqlORM 基类(已经封闭metadata) #直接创建中间间表并返回表的实例 HostUser2Group主动关联HostUser与Group(被关联) #机器的帐户(eg:A.root,A.mysql,B.root...)与主机组(g1,g2...)多对多关联 HostUser2Group = Table('hostuser_to_group', Base.metadata, Column('hostuser_id', ForeignKey('host_user.id'), primary_key=True), Column('group_id', ForeignKey('group.id'), primary_key=True), #一个表为什么能创建两个主键(其实是两个列同时作为主键,非空且唯一) #PRIMARY KEY (hostuser_id, group_id), ) #堡垒机用户信息表(eg:运维人员,u1,u2...)与主机组(g1,g2...)多对多关联 UserInfor2Group = Table('userinfor_to_group', Base.metadata, Column('userinfor_id', ForeignKey('user_infor.id'), primary_key=True), Column('group_id', ForeignKey('group.id'), primary_key=True), ) #堡垒机用户信息表(eg:运维人员,u1,u2...)与主机的帐户(eg:oracle...)多对多关联 #需求,主机的oracle坏了,需求厂家的工作人员的维修。此时工作人员帐户跳过分组进入主机的oracle帐户 UserInfor2HostUser = Table('userinfor_to_hostuser', Base.metadata, Column('userinfor_id', ForeignKey('user_infor.id'), primary_key=True), Column('hostuser_id', ForeignKey('host_user.id'), primary_key=True), ) class Host(Base): #主机表 __tablename__ = "host" #表名 id = Column(Integer, primary_key=True, autoincrement=True) # 默认自增 hostname = Column(String(64), unique=True, nullable=False) #唯一且不为空 ip_addr = Column(String(128), unique=True, nullable=False) port = Column(Integer, default=22) def __repr__(self): return "<id=%s, hostname=%s, ip_addr=%s, port=%s>" % (self.id, self.hostname, self.ip_addr, self.port) class Group(Base): #主机组 __tablename__ = "group" id = Column(Integer,primary_key=True) name = Column(String(64), unique=True, nullable=False) def __repr__(self): return "<id=%s, name=%s>" % (self.id, self.name) class UserInfor(Base): #堡垒机用户信息表(eg:运维人员...) __tablename__ = "user_infor" id = Column(Integer,primary_key=True) username = Column(String(64), unique=True, nullable=False) password = Column(String(255), nullable=True) #255最长了,先做明文密码,后面改进再做密文 groups = relationship("Group", #关联Group表 secondary = UserInfor2Group, #关联第三方表 backref = "user_list") #双向关联,不用在Group类中再加这句代码 hostuser_list = relationship("HostUser", # 关联Group表 secondary=HostUser2Group, # 关联第三方表 backref="user_list") # 双向关联,不用在Group类中再加这句代码 def __repr__(self): return "<id=%s, username=%s>" % (self.id, self.username) class HostUser(Base): #机器帐户(eg:mysql,root...)信息表 __tablename__ = "host_user" id = Column(Integer,primary_key=True) host_id = Column(Integer, ForeignKey("host.id")) #关联外键主机(A主机下的mysql帐户...) #登陆方式有两种,通过密码,或通过KEY AuthTypes = [ (u'ssh-passwd',u'SSH/Password'), (u'ssh-key',u'SSH/KEY'), ] username = Column(String(64), unique=True, nullable=False) password = Column(String(255)) #255最长了,先做明文密码,后面改进再做密文,password可为空,当选KEY时 #HostUser通过groups可查看组的信息,反之通过hostuser_list可查看组对应主机帐户的信息 groups = relationship("Group", #关联Group表 secondary = UserInfor2HostUser, #关联第三方表 backref = "hostuser_list") #双向关联,不用在Group类中再加这句代码 #联合唯一, 约束(host_id, username)是唯一的;name是自己取的名 __table_args__ = (UniqueConstraint("host_id", "username", name="_host_username_uc"),) def __repr__(self): return "<id=%s, username=%s>" % (self.id, self.username) #日志表 class AuditLog(Base): __tablename__ = 'audit_log' id = Column(Integer, primary_key=True) user_id = Column(Integer, ForeignKey('user_infor.id')) #关联主机帐户表 hostuser_id = Column(Integer, ForeignKey('host_user.id')) """ action_choices = [ (0, 'CMD'), (1, 'Login'), (2, 'Logout'), (3, 'GetFile'), (4, 'SendFile'), (5, 'Exception'), ] """ action_choices2 = [ (u'cmd', u'CMD'), #cmd是存到数据库中的,CMD是显示给用户看的 (u'login', u'Login'), (u'logout', u'Logout'), # (3,'GetFile'), # (4,'SendFile'), # (5,'Exception'), ] #ChoiceType是第三方插件 action_type = Column(ChoiceType(action_choices2)) # action_type = Column(String(64)) contain = Column(String(255)) #包含命令cmd,登陆login,logout... date = Column(DateTime) #关联用户表 user_infor = relationship("UserInfor") #echo=True可以查看创建表的过程 engine = create_engine("mysql+pymysql://root:root@localhost:3306/fortress_machine", echo=True) Base.metadata.create_all(engine) # 创建所有表结构View Code
1. 补充-1
Python 中也可以所用 sys 的 sys.argv 来获取命令行参数:
import sys print("A:", len(sys.argv)) print("B:", str(sys.argv))
输出:
C:\Users\Administrator\PycharmProjects\laonanhai\堡垒机\LittleFinger\modules>python test.py aa bb A: 3 B: ['test.py', 'aa', 'bb'] C:\Users\Administrator\PycharmProjects\laonanhai\堡垒机\LittleFinger\modules>python test.py aa bb A: 3 B: ['test.py', 'aa', 'bb']
若加上下面这句代码:
print("C:",sys.argv[1])
输出:
C:\Users\Administrator\PycharmProjects\laonanhai\堡垒机\LittleFinger\modules>python test.py aa bb A: 3 B: ['test.py', 'aa', 'bb'] C: aa
2. 补充-2
使用密码登录,每次都必须输入密码,非常麻烦。好在SSH还提供了公钥登录,可以省去输入密码的步骤。
所谓"公钥登录",原理很简单,就是用户将自己的公钥储存在远程主机上。登录的时候,远程主机会向用户发送一段随机字符串,用户用自己的私钥加密后,再发回来。远程主机用事先储存的公钥进行解密,如果成功,就证明用户是可信的,直接允许登录shell,不再要求密码。
这种方法要求用户必须提供自己的公钥。如果没有现成的,可以直接用ssh-keygen生成一个:
$ ssh-keygen
运行上面的命令以后,系统会出现一系列提示,可以一路回车。其中有一个问题是,要不要对私钥设置口令(passphrase),如果担心私钥的安全,这里可以设置一个。运行结束以后,在$HOME/.ssh/目录下,会新生成两个文件:id_rsa.pub和id_rsa。前者是你的公钥,后者是你的私钥。
这时再输入下面的命令,将公钥传送到远程主机host上面:
$ ssh-copy-id user@host
好了,从此你再登录,就不需要输入密码了。
参考博客: http://www.cnblogs.com/alex3714/articles/5286889.html
坑爹啊,alex大王的表结构设计和我的不一样……代码看得我一脸蒙比。
完整示例代码: https://github.com/triaquae/py3_training/tree/master/%E5%A0%A1%E5%9E%92%E6%9C%BA
如果有做过这个项目或将要做这个小项目的,博客写得比较详细的,可以发博客链接给我,让我学习学习,万分感谢。
待更新……