前景介绍
到目前为止,很多公司对堡垒机依然不太感冒,其实是没有充分认识到堡垒机在IT管理中的重要作用的,很多人觉得,堡垒机就是跳板机,其实这个认识是不全面的,跳板功能只是堡垒机所具备的功能属性中的其中一项而已,下面我就给大家介绍一下堡垒机的重要性,以帮助大家参考自己公司的业务是否需要部署堡垒机。
堡垒机有以下两个至关重要的功能:
权限管理
当你公司的服务器变的越来越多后,需要操作这些服务器的人就肯定不只是一个运维人员,同时也可能包括多个开发人员,那么这么多的人操作业务系统,如果权限分配不当就会存在很大的安全风险,举几个场景例子:
- 设想你们公司有300台Linux服务器,A开发人员需要登录其中5台WEB服务器查看日志或进行问题追踪等事务,同时对另外10台hadoop服务器有root权限,在有300台服务器规模的网络中,按常理来讲你是已经使用了ldap权限统一认证的,你如何使这个开发人员只能以普通用户的身份登录5台web服务器,并且同时允许他以管理员的身份登录另外10台hadoop服务器呢?并且同时他对其它剩下的200多台服务器没有访问权限
- 目前据我了解,很多公司的运维团队为了方面,整个运维团队的运维人员还是共享同一套root密码,这样内部信任机制虽然使大家的工作方便了,但同时存在着极大的安全隐患,很多情况下,一个运维人员只需要管理固定数量的服务器,毕竟公司分为不同的业务线,不同的运维人员管理的业务线也不同,但如果共享一套root密码,其实就等于无限放大了每个运维人员的权限,也就是说,如果某个运维人员想干坏事的话,他可以在几分钟内把整个公司的业务停转,甚至数据都给删除掉。为了降低风险,于是有人想到,把不同业务线的root密码改掉就ok了么,也就是每个业务线的运维人员只知道自己的密码,这当然是最简单有效的方式,但问题是如果你同时用了ldap,这样做又比较麻烦,即使你设置了root不通过ldap认证,那新问题就是,每次有运维人员离职,他所在的业务线的密码都需要重新改一次。
其实上面的问题,我觉得可以很简单的通过堡垒机来实现,收回所有人员的直接登录服务器的权限,所有的登录动作都通过堡垒机授权,运维人员或开发人员不知道远程服务器的密码,这些远程机器的用户信息都绑定在了堡垒机上,堡垒机用户只能看到他能用什么权限访问哪些远程服务器。
在回收了运维或开发人员直接登录远程服务器的权限后,其实就等于你们公司生产系统的所有认证过程都通过堡垒机来完成了,堡垒机等于成了你们生产系统的SSO(single sign on)模块了。你只需要在堡垒机上添加几条规则就能实现以下权限控制了:
- 允许A开发人员通过普通用户登录5台web服务器,通过root权限登录10台hadoop服务器,但对其余的服务器无任务访问权限
- 多个运维人员可以共享一个root账户,但是依然能分辨出分别是谁在哪些服务器上操作了哪些命令,因为堡垒机账户是每个人独有的,也就是说虽然所有运维人员共享了一同一个远程root账户,但由于他们用的堡垒账户都是自己独有的,因此依然可以通过堡垒机控制每个运维人员访问不同的机器。
审计管理
审计管理其实很简单,就是把用户的所有操作都纪录下来,以备日后的审计或者事故后的追责。在纪录用户操作的过程中有一个问题要注意,就是这个纪录对于操作用户来讲是不可见的,什么意思?就是指,无论用户愿不愿意,他的操作都会被纪录下来,并且,他自己如果不想操作被纪录下来,或想删除已纪录的内容,这些都是他做不到的,这就要求操作日志对用户来讲是不可见和不可访问的,通过堡垒机就可以很好的实现。
堡垒机架构
堡垒机的主要作用权限控制和用户行为审计,堡垒机就像一个城堡的大门,城堡里的所有建筑就是你不同的业务系统 , 每个想进入城堡的人都必须经过城堡大门并经过大门守卫的授权,每个进入城堡的人必须且只能严格按守卫的分配进入指定的建筑,且每个建筑物还有自己的权限访问控制,不同级别的人可以到建筑物里不同楼层的访问级别也是不一样的。还有就是,每个进入城堡的人的所有行为和足迹都会被严格的监控和纪录下来,一旦发生犯罪事件,城堡管理人员就可以通过这些监控纪录来追踪责任人。
堡垒要想成功完全记到他的作用,只靠堡垒机本身是不够的, 还需要一系列安全上对用户进行限制的配合,堡垒机部署上后,同时要确保你的网络达到以下条件:
- 所有人包括运维、开发等任何需要访问业务系统的人员,只能通过堡垒机访问业务系统
- 回收所有对业务系统的访问权限,做到除了堡垒机管理人员,没有人知道业务系统任何机器的登录密码
- 网络上限制所有人员只能通过堡垒机的跳转才能访问业务系统
- 确保除了堡垒机管理员之外,所有其它人对堡垒机本身无任何操作权限,只有一个登录跳转功能
- 确保用户的操作纪录不能被用户自己以任何方式获取到并篡改
堡垒机功能实现需求
- 兼顾业务安全目标与用户体验,堡垒机部署后,不应使用户访问业务系统的访问变的复杂,否则工作将很难推进,因为没人喜欢改变现状,尤其是改变后生活变得更艰难
- 保证堡垒机稳定安全运行, 没有100%的把握,不要上线任何新系统,即使有100%把握,也要做好最坏的打算,想好故障预案
功能需求:
- 所有的用户操作日志要保留在数据库中
- 每个用户登录堡垒机后,只需要选择具体要访问的设置,就连接上了,不需要再输入目标机器的访问密码
- 允许用户对不同的目标设备有不同的访问权限,例:
- 对10.0.2.34 有mysql 用户的权限
- 对192.168.3.22 有root用户的权限
- 对172.33.24.55 没任何权限
- 分组管理,即可以对设置进行分组,允许用户访问某组机器,但对组里的不同机器依然有不同的访问权限
表结构设计:
表结构示例代码:
1 #! /usr/bin/env python3
2 # -*- coding:utf-8 -*-
3
4 from sqlalchemy import Table, Column, Enum,Integer,String,DateTime, ForeignKey,UniqueConstraint
5 from sqlalchemy.orm import relationship,sessionmaker
6 from sqlalchemy.ext.declarative import declarative_base
7 from sqlalchemy_utils import ChoiceType
8 from sqlalchemy import create_engine
9
10
11 # 该模块主要定义程序所需的表,及它们之间的相互关系
12
13
14 Base = declarative_base()
15
16 # 主机和主机账户多对多
17 user_m2m_bindhost = Table('user_m2m_bindhost', Base.metadata,
18 Column('userprofile_id', Integer, ForeignKey('user_profile.id')),
19 Column('bindhost_id', Integer, ForeignKey('bind_host.id')),
20 )
21
22 # 主机和主机账户联合唯一表 与 主机分组 多对多
23 bindhost_m2m_hostgroup = Table('bindhost_m2m_hostgroup', Base.metadata,
24 Column('bindhost_id', Integer, ForeignKey('bind_host.id')),
25 Column('hostgroup_id', Integer, ForeignKey('host_group.id')),
26 )
27
28 # 主机账户表 与 主机分组 多对多
29 user_m2m_hostgroup = Table('userprofile_m2m_hostgroup', Base.metadata,
30 Column('userprofile_id', Integer, ForeignKey('user_profile.id')),
31 Column('hostgroup_id', Integer, ForeignKey('host_group.id')),
32 )
33
34
35 class Host(Base):
36 """定义主机表"""
37 __tablename__ = 'host'
38 id = Column(Integer,primary_key=True)
39 hostname = Column(String(64),unique=True)
40 ip = Column(String(64),unique=True)
41 port = Column(Integer,default=22)
42
43 def __repr__(self):
44 return self.hostname
45
46
47 class HostGroup(Base):
48 """主机分组表"""
49 __tablename__ = 'host_group'
50 id = Column(Integer, primary_key=True)
51 name = Column(String(64), unique=True)
52 # 主机组与(主机及用户联合)多对多,这里可反查。
53 bind_hosts = relationship("BindHost",secondary="bindhost_m2m_hostgroup",backref="host_groups")
54
55 def __repr__(self):
56 return self.name
57
58
59 class RemoteUser(Base):
60 """主机上的用户名"""
61 __tablename__ = 'remote_user'
62 __table_args__ = (UniqueConstraint('auth_type', 'username','password', name='_user_passwd_uc'),)
63
64 id = Column(Integer, primary_key=True)
65 AuthTypes = [
66 ('ssh-password','SSH/Password'),
67 ('ssh-key','SSH/KEY'),
68 ]
69 auth_type = Column(ChoiceType(AuthTypes))
70 username = Column(String(32))
71 password = Column(String(128))
72
73 def __repr__(self):
74 return self.username
75
76
77 class BindHost(Base):
78 '''
79 远程主机ip和主机上的账户表,两者联合唯一。
80 192.168.1.11 web
81 192.168.1.11 mysql
82
83 '''
84 __tablename__ = "bind_host"
85 __table_args__ = (UniqueConstraint('host_id','remoteuser_id', name='_host_remoteuser_uc'),)
86
87 id = Column(Integer, primary_key=True)
88 host_id = Column(Integer,ForeignKey('host.id'))
89 remoteuser_id = Column(Integer, ForeignKey('remote_user.id'))
90 host = relationship("Host",backref="bind_hosts")
91 remote_user = relationship("RemoteUser",backref="bind_hosts")
92 audit_logs = relationship('AuditLog')
93 def __repr__(self):
94 return "<%s -- %s >" % (self.host.ip,self.remote_user.username)
95
96
97 class UserProfile(Base):
98 """堡垒机账户"""
99 __tablename__ = 'user_profile'
100
101 id = Column(Integer, primary_key=True)
102 username = Column(String(32),unique=True)
103 password = Column(String(128))
104 bind_hosts = relationship("BindHost", secondary='user_m2m_bindhost',backref="user_profiles")
105 host_groups = relationship("HostGroup",secondary="userprofile_m2m_hostgroup",backref="user_profiles")
106
107 def __repr__(self):
108 return self.username
109
110
111
112 class AuditLog(Base):
113 """日志记录"""
114 __tablename__ = 'audit_log'
115 id = Column(Integer, primary_key=True)
116 user_id = Column(Integer, ForeignKey('user_profile.id'))
117 bind_host_id = Column(Integer, ForeignKey('bind_host.id'))
118 action_choices = [
119 (0, 'CMD'),
120 (1, 'Login'),
121 (2, 'Logout'),
122 (3, 'GetFile'),
123 (4, 'SendFile'),
124 (5, 'Exception'),
125 ]
126 action_choices2 = [
127 (u'cmd', u'CMD'),
128 (u'login', u'Login'),
129 (u'logout', u'Logout'),
130 # (3,'GetFile'),
131 # (4,'SendFile'),
132 # (5,'Exception'),
133 ]
134 action_type = Column(ChoiceType(action_choices2))
135 # action_type = Column(String(64))
136 cmd = Column(String(255))
137 date = Column(DateTime)
138
139 user_profile = relationship("UserProfile")
140 bind_host = relationship("BindHost")
141
142 def __repr__(self):
143 return "<bind_host_id:%s -- action_type:%s -- cmd:%s -- date:%s>" % (self.bind_host_id,self.action_type,
144 self.cmd,self.date)
145
146
147 if __name__ == "__main__":
148 engine = create_engine("mysql+pymysql://root:Root-123@192.168.100.156/test_db?charset=utf8" )
149 Base.metadata.drop_all(engine)
150 Base.metadata.create_all(engine) # 创建表结构
151 # Base.metadata.drop_all(engine)
152
153 h1 = Host(hostname="host1", ip="192.168.100.156")
154 h2 = Host(hostname="host2", ip="192.168.100.157")
155 u1 = RemoteUser(auth_type='ssh-password', username="root", password="admin")
156 u2 = RemoteUser(auth_type='ssh-password', username="root", password="admin123")
157 b1 = BindHost(host_id=1, remoteuser_id=1)
158 b2 = BindHost(host_id=1, remoteuser_id=2)
159 b3 = BindHost(host_id=2, remoteuser_id=2)
160
161 g1 = HostGroup(name="bj_group")
162 g1.bind_hosts = [b1]
163 g2 = HostGroup(name="sh_group")
164 g2.bind_hosts = [b2,b3]
165
166 uh1 = UserProfile(username="lyy1", password="123")
167 uh1.bind_hosts = [b1,b2,b3]
168 uh1.host_groups = [g1,g2]
169
170 SessionCls = sessionmaker(bind=engine) # 创建与数据库的会话session class ,注意,这里返回给session的是个class,不是实例
171 session = SessionCls()
172
173 session.add_all([h1,h2,u1,u2,b1,b2,b3,g1,g2,uh1])
174 session.commit()