堡壘機表結構設計


前言: 好幾天沒寫博客了哈,這篇博客主要介紹堡壘機的功能與作用。之前沒聽過堡壘機的可以看看,還較詳細地講了數據庫表結構的設計,寫完這篇博客,感覺數據庫真是博大精深……本來還想把這個項目做完的……but ...待更新吧……

 

一、前景介紹

到目前為止,很多公司對堡壘機依然不太感冒,其實是沒有充分認識到堡壘機在IT管理中的重要作用的,很多人覺得,堡壘機就是跳板機,其實這個認識是不全面的,跳板功能只是堡壘機所具備的功能屬性中的其中一項而已,下面我就給大家介紹一下堡壘機的重要性,以幫助大家參考自己公司的業務是否需要部署堡壘機。
堡壘機有以下兩個至關重要的功能:

  • 權限管理
  • 審計功能

 

1. 權限管理

當你公司的服務器變的越來越多后,需要操作這些服務器的人就肯定不只是一個運維人員,同時也可能包括多個開發人員,那么這么多的人操作業務系統,如果權限分配不當就會存在很大的安全風險,舉幾個場景例子:

  1. 設想你們公司有300台Linux服務器,A開發人員需要登錄其中5台WEB服務器查看日志或進行問題追蹤等事務,同時對另外10台hadoop服務器有root權限,在有300台服務器規模的網絡中,按常理來講你是已經使用了ldap權限統一認證的,你如何使這個開發人員只能以普通用戶的身份登錄5台web服務器,並且同時允許他以管理員的身份登錄另外10台hadoop服務器呢?並且同時他對其它剩下的200多台服務器沒有訪問權限
  2. 目前據我了解,很多公司的運維團隊為了方便,整個運維團隊的運維人員還是共享同一套root密碼,這樣內部信任機制雖然使大家的工作方便了,但同時存在着極大的安全隱患,很多情況下,一個運維人員只需要管理固定數量的服務器,畢竟公司分為不同的業務線,不同的運維人員管理的業務線也不同,但如果共享一套root密碼,其實就等於無限放大了每個運維人員的權限,也就是說,如果某個運維人員想干壞事的話,他可以在幾分鍾內把整個公司的業務停轉,甚至數據都給刪除掉。為了降低風險,於是有人想到,把不同業務線的root密碼改掉就ok了么,也就是每個業務線的運維人員只知道自己的密碼,這當然是最簡單有效的方式,但問題是如果你同時用了ldap,這樣做又比較麻煩,即使你設置了root不通過ldap認證,那新問題就是,每次有運維人員離職,他所在的業務線的密碼都需要重新改一次

 


其實上面的問題,我覺得可以很簡單的通過堡壘機來實現,收回所有人員的直接登錄服務器的權限,所有的登錄動作都通過堡壘機授權,運維人員或開發人員不知道遠程服務器的密碼,這些遠程機器的用戶信息都綁定在了堡壘機上,堡壘機用戶只能看到他能用什么權限訪問哪些遠程服務器。

 


在回收了運維或開發人員直接登錄遠程服務器的權限后,其實就等於你們公司生產系統的所有認證過程都通過堡壘機來完成了,堡壘機等於成了你們生產系統的SSO(single sign on)模塊了。你只需要在堡壘機上添加幾條規則就能實現以下權限控制了:

  1. 允許A開發人員通過普通用戶登錄5台web服務器,通過root權限登錄10台hadoop服務器,但對其余的服務器無任務訪問權限
  2. 多個運維人員可以共享一個root賬戶,但是依然能分辨出分別是誰在哪些服務器上操作了哪些命令,因為堡壘機賬戶是每個人獨有的,也就是說雖然所有運維人員共享了一同一個遠程root賬戶,但由於他們用的堡壘賬戶都是自己獨有的,因此依然可以通過堡壘機控制每個運維人員訪問不同的機器。

 

 

2. 審計管理

審計管理其實很簡單,就是把用戶的所有操作都紀錄下來,以備日后的審計或者事故后的追責。在紀錄用戶操作的過程中有一個問題要注意,就是這個紀錄對於操作用戶來講是不可見的,什么意思?就是指,無論用戶願不願意,他的操作都會被紀錄下來,並且,他自己如果不想操作被紀錄下來,或想刪除已紀錄的內容,這些都是他做不到的,這就要求操作日志對用戶來講是不可見和不可訪問的,通過堡壘機就可以很好的實現。

 

 

二、堡壘機的架構

堡壘機的主要作用權限控制和用戶行為審計,堡壘機就像一個城堡的大門,城堡里的所有建築就是你不同的業務系統 , 每個想進入城堡的人都必須經過城堡大門並經過大門守衛的授權,每個進入城堡的人必須且只能嚴格按守衛的分配進入指定的建築,且每個建築物還有自己的權限訪問控制,不同級別的人可以到建築物里不同樓層的訪問級別也是不一樣的。還有就是,每個進入城堡的人的所有行為和足跡都會被嚴格的監控和紀錄下來,一旦發生犯罪事件,城堡管理人員就可以通過這些監控紀錄來追蹤責任人。 
 

防火牆:

配置acl(安全控制):只能從外進入堡壘機,看到主機列表,選擇后可進入。


堡壘要想成功完全起到他的作用,只靠堡壘機本身是不夠的, 還需要一系列安全上對用戶進行限制的配合,堡壘機部署上后,同時要確保你的網絡達到以下條件:

  1. 所有人包括運維、開發等任何需要訪問業務系統的人員,只能通過堡壘機訪問業務系統
  2. 回收所有對業務系統的訪問權限,做到除了堡壘機管理人員沒有人知道業務系統任何機器的登錄密碼
  3. 網絡上限制所有人員只能通過堡壘機的跳轉才能訪問業務系統 
  4. 確保除了堡壘機管理員之外,所有其它人對堡壘機本身無任何操作權限,只有一個登錄跳轉功能
  5. 確保用戶的操作紀錄不能被用戶自己以任何方式獲取到並篡改

 

 

三、堡壘機功能實現需求

業務需求:

  1. 兼顧業務安全目標與用戶體驗,堡壘機部署后,不應使用戶業務系統的訪問變的復雜,否則工作將很難推進,因為沒人喜歡改變現狀,尤其是改變后生活變得更艱難
  2. 保證堡壘機穩定安全運行, 沒有100%的把握,不要上線任何新系統,即使有100%把握,也要做好最壞的打算,想好故障預案


功能需求:

  1. 所有的用戶操作日志要保留在數據庫中
  2. 每個用戶登錄堡壘機后,只需要選擇具體要訪問的設置,就連接上了,不需要再輸入目標機器的訪問密碼
  3. 允許用戶對不同的目標設備有不同的訪問權限,例:
    1. 對10.0.2.34 有mysql 用戶的權限
    2. 對192.168.3.22 有root用戶的權限
    3. 對172.33.24.55 沒任何權限
  4. 分組管理,即可以對設置進行分組,允許用戶訪問某組機器,但對組里的不同機器依然有不同的訪問權限

 

 

看到上面的需求后,我徹底懵比了  )/_\(

看到我上面寫那么多文字,不知你看懂沒,反正我剛開始沒看懂,后來研究下表結構后,再來看,感覺意會到了不少……注意我上面加紅色的字……

 

 

四、設計表結構

所有涉及mysql等數據庫的產品,都必須先設計好表結構。怎么設計呢? 先大體看下面這個圖:

主機表Host:

id hostname ip_addr port
1 A    
2 B    
3 C    

組表Group:

id name
1 g1
2 g2
3 g3

用戶表UserInfor:

id usrename password
1 u1  
2 u2  
3 u3  

主機帳戶表HostUser: egA主機下多個帳戶(eg:mysql,root...)

id username password host_id
1 root    
2 mysql    
3 oracle    

日志表AuditLog:

id user_id hostuser_id action_type contain date
           
           
           

 

 

 

1. 先來講主機與用戶:

主機 主機帳戶 密碼
A root 123
B root qwe
B mysql 123

以第一條為例,代表A主機下有個root帳戶,該帳戶密碼為123。

若此時再增入一條記錄(第4條數據):

主機 主機帳戶 密碼
A root 123
B root qwe
B mysql 123
A root aaa

此時問題來了,第3條數據說: B主機下有個mysql帳戶,密碼為123;而第4條數據說: A主機下有個root帳戶,密碼為aaa。此時我懵比了,我到底該聽誰的?? 實際上A主機上不可能有兩個root用戶!!

解決方法:

對主機(host_id)與主機帳戶(username)進行聯合唯一。下面的代碼是放在HostUser表結構中: 需要導入UniqueConstraint,重點看下第4,18行代碼:

 1 class HostUser(Base):  #機器帳戶(eg:mysql,root...)信息表
 2     __tablename__ = "host_user"
 3     id = Column(Integer,primary_key=True)  4     host_id = Column(Integer, ForeignKey("host.id"))  #關聯外鍵主機(A主機下的mysql帳戶...)
 5     #登陸方式有兩種,通過密碼,或通過KEY
 6     AuthTypes = [  7         (u'ssh-passwd',u'SSH/Password'),  8         (u'ssh-key',u'SSH/KEY'),  9  ] 10     username = Column(String(64), unique=True, nullable=False) 11     password = Column(String(255))   #255最長了,先做明文密碼,后面改進再做密文,password可為空,當選KEY時
12     #HostUser通過groups可查看組的信息,反之通過hostuser_list可查看組對應主機帳戶的信息
13     groups = relationship("Group",  #關聯Group表
14                            secondary = UserInfor2HostUser,  #關聯第三方表
15                            backref = "hostuser_list")  #雙向關聯,不用在Group類中再加這句代碼
16 
17     #聯合唯一, 約束(host_id, username)是唯一的;name是自己取的名
18     __table_args__ = (UniqueConstraint("host_id", "username", name="_host_username_uc"),) 19 
20     def __repr__(self): 21         return "<id=%s, username=%s>" % (self.id, self.username)

 

2. 權限分配

因為之前做過主機管理工具,所以對於主機與組,我立馬就想到主機與組進行關聯。主機B可以屬於多個組g1,g2; 一個組g1可以對應多個主機A,B; GOOD! 多對多關聯唄。

主機名 組名
A g1
B g1
B g2

打臉時該到了,B機器有3個帳戶(eg: root,mysql,ngnix), 此時代表只有有g1組的訪問權限就擁有B機器下所有帳戶的訪問權限。這屬於沒仔細分析需求,得把代碼刪掉重新寫過……

需求是最最重要的! 前面有這樣一條需求: 可以對設置進行分組,允許用戶訪問某組機器,但對組里的不同機器依然有不同的訪問權限。

解決方法:

A.root g1
B.ngnix g1
B.mysql g2

根據上表,A機器的root權限分配給g1; 用戶若有g1的訪問權限,就有A的root, B的ngnix權限。

注意: 可看前面的第4行代碼,主機帳戶有外鍵關聯主機

host_id = Column(Integer, ForeignKey("host.id"))  #關聯外鍵主機(A主機下的mysql帳戶...)

 

上表是主機帳戶表與組表的關聯,屬於多對多關聯,因為沒用到django的orm,我是用sqlalchemy,所以需要自己生成中間表HostUser2Group.  至此,組與主機的權限分配OK.

 

3. 組與用戶的權限

這個點挺簡單的啦。

g1 u1
g1 u2
g2 u1

用戶u1有g1,g2組的訪問權限,用戶u2有g1的訪問權限。g1對應u1,u2; u1對應g1,g2。顯然是多對多關聯啦。我定義中間表UserInfor2Group

 

 

4. 主機帳戶與用戶關聯

需求場景:

現在一個組有100台主機,其中一台主機A的oracle出現了不可言傳的損壞。公司是中移動這樣的大公司,此時急需oracle廠商的技術支持,現在打電話給oracle的技術人員O,O當然需要登陸主機A進行修復查看啦。按我之前的實現,此時堡壘機的管理者要先為O分配一個組,組包含該損壞的主機A。這這這,太麻煩了,而且O一修復完,堡壘機的管理者還需要將剛剛為O分配的組刪除。

解決方法:

將特定的用戶與主機帳戶表直接進行關聯,不用再通過組。

B.oracle u1
B.root u1
B.oracle u2

顯然,這是多對多關聯~~ 我定義了中間表UserInfor2HostUser

 

5. 日志表

關於日志表的需求有: 所有的用戶操作日志要保留在數據庫中。

 1 #日志表
 2 class AuditLog(Base):  3     __tablename__ = 'audit_log'
 4     id = Column(Integer, primary_key=True)  5     user_id = Column(Integer, ForeignKey('user_infor.id'))  6     #關聯主機帳戶表
 7     hostuser_id = Column(Integer, ForeignKey('host_user.id'))  8     """
 9  action_choices = [ 10  (0, 'CMD'), 11  (1, 'Login'), 12  (2, 'Logout'), 13  (3, 'GetFile'), 14  (4, 'SendFile'), 15  (5, 'Exception'), 16  ] 17     """
18     action_choices2 = [ 19         (u'cmd', u'CMD'),  #cmd是存到數據庫中的,CMD是顯示給用戶看的
20         (u'login', u'Login'), 21         (u'logout', u'Logout'), 22         # (3,'GetFile'),
23         # (4,'SendFile'),
24         # (5,'Exception'),
25  ] 26     #ChoiceType是第三方插件
27     action_type = Column(ChoiceType(action_choices2)) 28     # action_type = Column(String(64))
29     contain = Column(String(255))  #包含命令cmd,登陸login,logout...
30     date = Column(DateTime) 31     #關聯用戶表
32     user_infor = relationship("UserInfor")

 

日志表,肯定要記錄是哪個用戶操作的吧! 因此外鍵關聯用戶的id

user_id = Column(Integer, ForeignKey('user_infor.id'))

你肯定要知道用戶是在哪台機器上操作的吧,單單知道在哪台機器操作是不夠的,還要知道在該機器下哪個帳戶(eg:root,mysql...), 因此需要外鍵關聯主機帳戶表

hostuser_id = Column(Integer, ForeignKey('host_user.id'))

  

表結構總代碼: 

  1 """
  2 表結構
  3 """
  4 
  5 from sqlalchemy import create_engine, Table
  6 from sqlalchemy.ext.declarative import declarative_base
  7 from sqlalchemy import Column, Integer, String,ForeignKey,UniqueConstraint,\
  8     DateTime
  9 from sqlalchemy.orm import sessionmaker, relationship
 10 from sqlalchemy_utils import ChoiceType
 11 
 12 
 13 Base = declarative_base()  # 生成一個SqlORM 基類(已經封閉metadata)
 14 
 15 
 16 #直接創建中間間表並返回表的實例  HostUser2Group主動關聯HostUser與Group(被關聯)
 17 #機器的帳戶(eg:A.root,A.mysql,B.root...)與主機組(g1,g2...)多對多關聯
 18 HostUser2Group = Table('hostuser_to_group', Base.metadata,
 19     Column('hostuser_id', ForeignKey('host_user.id'), primary_key=True),
 20     Column('group_id', ForeignKey('group.id'), primary_key=True),
 21     #一個表為什么能創建兩個主鍵(其實是兩個列同時作為主鍵,非空且唯一)
 22     #PRIMARY KEY (hostuser_id, group_id),
 23 )
 24 
 25 #堡壘機用戶信息表(eg:運維人員,u1,u2...)與主機組(g1,g2...)多對多關聯
 26 UserInfor2Group = Table('userinfor_to_group', Base.metadata,
 27     Column('userinfor_id', ForeignKey('user_infor.id'), primary_key=True),
 28     Column('group_id', ForeignKey('group.id'), primary_key=True),
 29 )
 30 
 31 #堡壘機用戶信息表(eg:運維人員,u1,u2...)與主機的帳戶(eg:oracle...)多對多關聯
 32 #需求,主機的oracle壞了,需求廠家的工作人員的維修。此時工作人員帳戶跳過分組進入主機的oracle帳戶
 33 UserInfor2HostUser = Table('userinfor_to_hostuser', Base.metadata,
 34     Column('userinfor_id', ForeignKey('user_infor.id'), primary_key=True),
 35     Column('hostuser_id', ForeignKey('host_user.id'), primary_key=True),
 36 )
 37 
 38 
 39 class Host(Base):   #主機表
 40     __tablename__ = "host"  #表名
 41     id = Column(Integer, primary_key=True, autoincrement=True)  # 默認自增
 42     hostname = Column(String(64), unique=True, nullable=False)  #唯一且不為空
 43     ip_addr = Column(String(128), unique=True, nullable=False)
 44     port = Column(Integer, default=22)
 45 
 46     def __repr__(self):
 47         return "<id=%s, hostname=%s, ip_addr=%s, port=%s>" % (self.id,
 48                                                               self.hostname,
 49                                                               self.ip_addr,
 50                                                               self.port)
 51 
 52 
 53 class Group(Base):  #主機組
 54     __tablename__ = "group"
 55     id = Column(Integer,primary_key=True)
 56     name = Column(String(64), unique=True, nullable=False)
 57 
 58     def __repr__(self):
 59         return "<id=%s, name=%s>" % (self.id, self.name)
 60 
 61 
 62 class UserInfor(Base):  #堡壘機用戶信息表(eg:運維人員...)
 63     __tablename__ = "user_infor"
 64     id = Column(Integer,primary_key=True)
 65     username = Column(String(64), unique=True, nullable=False)
 66     password = Column(String(255), nullable=True)   #255最長了,先做明文密碼,后面改進再做密文
 67     groups = relationship("Group",  #關聯Group表
 68                            secondary = UserInfor2Group,  #關聯第三方表
 69                            backref = "user_list")  #雙向關聯,不用在Group類中再加這句代碼
 70     hostuser_list = relationship("HostUser",  # 關聯Group表
 71                                  secondary=HostUser2Group,  # 關聯第三方表
 72                                  backref="user_list")  # 雙向關聯,不用在Group類中再加這句代碼
 73     def __repr__(self):
 74         return "<id=%s, username=%s>" % (self.id, self.username)
 75 
 76 
 77 class HostUser(Base):  #機器帳戶(eg:mysql,root...)信息表
 78     __tablename__ = "host_user"
 79     id = Column(Integer,primary_key=True)
 80     host_id = Column(Integer, ForeignKey("host.id"))  #關聯外鍵主機(A主機下的mysql帳戶...)
 81     #登陸方式有兩種,通過密碼,或通過KEY
 82     AuthTypes = [
 83         (u'ssh-passwd',u'SSH/Password'),
 84         (u'ssh-key',u'SSH/KEY'),
 85     ]
 86     username = Column(String(64), unique=True, nullable=False)
 87     password = Column(String(255))   #255最長了,先做明文密碼,后面改進再做密文,password可為空,當選KEY時
 88     #HostUser通過groups可查看組的信息,反之通過hostuser_list可查看組對應主機帳戶的信息
 89     groups = relationship("Group",  #關聯Group表
 90                            secondary = UserInfor2HostUser,  #關聯第三方表
 91                            backref = "hostuser_list")  #雙向關聯,不用在Group類中再加這句代碼
 92 
 93     #聯合唯一, 約束(host_id, username)是唯一的;name是自己取的名
 94     __table_args__ = (UniqueConstraint("host_id", "username", name="_host_username_uc"),)
 95 
 96     def __repr__(self):
 97         return "<id=%s, username=%s>" % (self.id, self.username)
 98 
 99 
100 #日志表
101 class AuditLog(Base):
102     __tablename__ = 'audit_log'
103     id = Column(Integer, primary_key=True)
104     user_id = Column(Integer, ForeignKey('user_infor.id'))
105     #關聯主機帳戶表
106     hostuser_id = Column(Integer, ForeignKey('host_user.id'))
107     """
108     action_choices = [
109         (0, 'CMD'),
110         (1, 'Login'),
111         (2, 'Logout'),
112         (3, 'GetFile'),
113         (4, 'SendFile'),
114         (5, 'Exception'),
115     ]
116     """
117     action_choices2 = [
118         (u'cmd', u'CMD'),  #cmd是存到數據庫中的,CMD是顯示給用戶看的
119         (u'login', u'Login'),
120         (u'logout', u'Logout'),
121         # (3,'GetFile'),
122         # (4,'SendFile'),
123         # (5,'Exception'),
124     ]
125     #ChoiceType是第三方插件
126     action_type = Column(ChoiceType(action_choices2))
127     # action_type = Column(String(64))
128     contain = Column(String(255))  #包含命令cmd,登陸login,logout...
129     date = Column(DateTime)
130     #關聯用戶表
131     user_infor = relationship("UserInfor")
132 
133 
134 
135 
136 
137 #echo=True可以查看創建表的過程
138 engine = create_engine("mysql+pymysql://root:root@localhost:3306/fortress_machine", echo=True)
139 
140 Base.metadata.create_all(engine)  # 創建所有表結構
View Code

 

五、補充

1. 補充-1

Python 中也可以所用 sys 的 sys.argv 來獲取命令行參數:

  • sys.argv 是命令行參數列表。
  • len(sys.argv) 是命令行參數個數。
1 import sys
2 
3 print("A:", len(sys.argv))
4 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公鑰登錄過程

使用密碼登錄,每次都必須輸入密碼,非常麻煩。好在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 

如果有做過這個項目或將要做這個小項目的,博客寫得比較詳細的,可以發博客鏈接給我,讓我學習學習,萬分感謝。

轉發注明出處: http://www.cnblogs.com/0zcl/p/6548142.html 

待更新……


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM