官方參考頁面:
https://docs.mongodb.com/v3.6/tutorial/enable-authentication/
https://docs.mongodb.com/v3.6/tutorial/enforce-keyfile-access-control-in-existing-replica-set/
前言:
前些年很多用戶對mongodb的安全意識是很淡漠的,也因此在前幾年出現了一些很嚴重的針對mongodb的攻擊。
本文以mongodb3.6為例,介紹mongodb目前的用戶驗證機制,當然用戶驗證只是mongodb安全體系的一部分,更加全面的安全內容參考:https://docs.mongodb.com/v3.6/security/
雖然mongodb提供了一系列的加強安全的措施,但實際上經常會用到的也就是用戶驗證(本文稱為client auth)和集群成員間的驗證(本文稱為internal auth)了,internal auth多使用keyfile,關於keyfile驗證參考上述的第二個官方鏈接即可,這種keyfile是集群節點間的一種權限驗證機制,是mongo實例級別的,會在最后介紹下。
關於加密算法:
1.集群成員間的internal authentication加密方式有keyfiles和x.509兩種,前者較多見,后者涉及TLS/SSL封裝,我沒用過。
2.用戶驗證的加密算法在4.0之前默認為SCRAM,4.0之后MONGODB-CR(3.0前的默認加密方式)已被廢棄,而x.509不但支持internal auth也支持client auth,但是很少使用。
本文主要介紹最核心的用戶驗證,這種用戶驗證的粒度是庫級別的。
一、如何創建用戶?
安裝MongoDB后auth認證默認是關閉的,此時admin庫是不可見的(舊版本不可見,新版本里只是admin下的用戶相關的system.users不可見),現創建一個超級帳號:
use admin //查看當前庫內已有用戶 show users //查看當前庫內可用的roles,默認只有built-in roles show roles //創建用戶,roles可以使用當前庫內的角色,或者其他庫內的角色 db.createUser({user: "root",pwd: "root",roles: [ { role: "root", db: "admin" } ]}) //如何修改密碼 db.changeUserPassword('root','rootNew'); //已有用戶新增和解除built-in roles db.grantRolesToUser('<username>', [{ role: '<built-in role>', db: 'admin' }]) db.revokeRolesFromUser( "<username>", [{ role: '<built-in role>', db: 'admin' }]) //刪除用戶命令如下,雖然所有庫的用戶信息全存在admin的system.users中,刪用戶時還是要use <庫名>才能刪除 use db_name db.dropUser("<username>")
創建用戶時需要牢記的幾點:
- 創建用戶必須指定庫名,即用戶是和庫綁定的。即便是超級用戶的創建也是如此,如果你創建root用戶時是在業務庫里,那你以后登錄root也只能指定業務庫的庫名了(超級賬戶一般建於admin庫下)。
- 創建用戶時指定的built-in roles也是與庫綁定的,指定的哪個庫的角色,就擁有哪個庫的操作權限,即在test庫內創建db.createUser({user: "test",pwd: "test",roles: [ { role: "read", db: "test1" } ]})只能讀test1下的collection不能讀test下的。
- 不同的庫下可以存在相同的用戶名,即admin.root用戶和test.root用戶是可以同時存在的。
- 登陸mongo shell時如果不指定庫名,默認登入的test庫(只在3.6驗證過,其他版本自行驗證),你需要use db_name后才能進行db.auth(),當然你可以選擇直接mongo db_name -u username -p password登陸。
- 一般來說創建一個root角色的超級用戶root即可(或一個__system權限的system用戶),創建一些普通權限的用戶做日常操作。
示例,創建包含多個角色的用戶: use test db.createUser( { user: "myTester", pwd: "xyz123", roles: [ { role: "readWrite", db: "test" }, { role: "read", db: "reporting" } ] } ) //這里第二個role是reporting.read,說明此用戶擁有reporting下的讀權限。 use admin db.createUser( { user: "myUserAdmin", pwd: "abc123", roles: [ { role: "userAdminAnyDatabase", db: "admin" }, "readWriteAnyDatabase" ] } ) //未指定db的role默認是使用當前庫下的角色的,這里其實readWriteAnyDatabase相當於{ role: "readWriteAnyDatabase", db: "admin" }
二、什么是角色(roles)?
https://docs.mongodb.com/manual/reference/built-in-roles/index.html
roles即一系列權限的集合,系統內置了一系列的built-in roles以便可以方便的為用戶授權,一般來說使用這些built-in roles創建用戶就可以了,除非你需要更細粒度的指定權限(那你可能需要下點功夫看官網文檔)。
Built-In Roles簡略介紹(詳細介紹參考以上官方鏈接): Database User Roles: read 提供本庫下所有非系統collection的讀權限,以及少數幾個系統collection的讀權限,例如system.indexes, system.js, and system.namespaces readWrite 提供本庫下所有非系統collection的讀寫權限,以及系統collection system.js的讀寫權限。 Database Administration Roles: dbAdmin 提供一些庫管理的權限,諸如索引創建,增刪集合,刪庫等等,並沒有對所有集合的讀權限,因此其實很少會用到。 userAdmin 提供在本庫下創建用戶、角色,刪除用戶、角色,修改密碼等一系列用戶相關的權限。 dbOwner 庫擁有者權限,即readWrite、dbAdmin、userAdmin角色的合體。 Cluster Administration Roles: clusterAdmin 集群管理權限,clusterManager、clusterMonitor、hostManager角色的合體,此外再加上dropDatabase權限。 clusterManager 集群管理者權限,提供諸如添加shard,刪除shard,修改副本集配置等權限。 clusterMonitor 集群監控權限,顧名思義擁有查看一系列集群狀態的權限。 hostManager 參考官網鏈接的解釋,很少使用。 Backup and Restoration Roles: backup 即進行數據備份的權限,實例級別的角色。 restore 即進行數據還原的權限,實例級別的角色,還包含很多與dbOwner重合的權限,參見官網中的相關解釋。 All-Database Roles: readAnyDatabase 提供針對除了local和config庫外所有其他庫的讀權限。 readWriteAnyDatabase 提供針對除了local和config庫外所有其他庫的讀寫權限。 userAdminAnyDatabase 同userAdmin角色,只不過范圍擴大到了所有庫。 dbAdminAnyDatabase 同dbAdmin角色,只不過范圍擴大到了所有庫。 Superuser Roles: root 即超級用戶的權限,擁有此權限你可以管理任意數據庫,可以將此角色理解為dbOwner of All Database。 Internal Role: __system 系統內置角色,擁有很高的權限(高於root),在做一些集群操作時可以使用此包含此角色的用戶 一般不推薦設置此角色的用戶,使用keyfile進行internal auth的副本集之間的交互就是使用此角色的。
一般來說應用使用的用戶只需要readWrite角色即可,DBA可使用root賬戶和__system賬戶進行諸如集群配置,備份恢復等操作。
如何查看用戶的權限:
use admin db.system.users.find() //所有庫的用戶全部存在admin的system.users中 或者 use db_name show users
三、如何開啟驗證登陸:
之前說了如何創建用戶和指定權限,那么如何使用戶登錄驗證生效呢?
在配置文件添加auth=true參數重啟之后即可生效(如果是集群環境只配置keyfile也可以),然后登陸:
use db_name //用戶必須切換到其相應的庫登陸。3.6版本中默認登陸的一般是test庫,所以要注意使用use dbname切換到用戶對應的庫
db.auth('xxx','xxx') //登陸相應的庫之后就可以操作數據啦
--或者直接在命令行中登陸:
mongo db_name -u username -p password
四、關於Localhost Exception
https://docs.mongodb.com/manual/core/security-users/#localhost-exception
mongodb配置文件中有個配置項enableLocalhostAuthBypass,其默認值為1(true)。
這個參數=1的含義是:
如果你設置了auth=true,但是還未創建user或者不知道原來的用戶密碼想新建一個,那么此參數允許你在本地登陸時創建用戶,然后使用此新建的用戶驗證登錄。
相應的如果此參數為0,那么如果你未創建用戶或者忘記了賬號密碼,那么即便在本地登陸mongo shell,也不允許你新建用戶。
因此官網建議將此參數設置為0或false,從而防止有人可以在服務器本地新建超級權限的用戶。
這個參數在初始安裝時有點用,例如你開啟了auth認證但是還沒建超級賬戶的場景,不過剛安裝完成時一般連auth都不會開,這個參數雞肋。
保險起見這里還是貼下enableLocalhostAuthBypass參數的設置方法:
在配置文件里添加setParameter=enableLocalhostAuthBypass=1,然后重啟(這是非YAML的設置方式,YAML的設置方式官網示例很完善了動手去查下就知道)。
https://docs.mongodb.com/v3.6/core/security-users/#sharded-cluster-users
mongodb還針對sharding集群提供了shard cluster user,普通的副本集內用戶稱作shard local user,集群用戶可以通過mongos登陸並操作整個集群的數據,但是shard local user只能操作特定的shard副本集,例如 cleanupOrphaned, compact, rs.reconfig()等操作可以使用本地用戶執行。
在mongos上創建的用戶屬於shard cluster user,在shard副本集本地節點創建的用戶就是shard local user了。在mongo2.6之后集群用戶是存儲在config server上的。
一般來說沒必要糾結集群用戶和普通應用用戶,對分片的集合使用集群用戶,不分片的使用本地用戶查詢某個shard副本集也可以。
mongodb官網是建議對於shard集群使用集群用戶連接mongos來操作集群,而本地用戶只用於進行單個shard的管理和配置。這應該是出於維護數據一致性的需求,對於那些未分片的集合操作本地shard應該是可以的,但是因為我生產上也沒部署過shard集群,所以實際生產中的最優實踐大家根據自己的實際情況抉擇。
更多的關於集群用戶的相關知識參見上述官網鏈接,這里不再詳述。
關於集群驗證:
在之前介紹了如何為mongo開啟用戶驗證,但是如果是replica或shard環境怎么辦?副本集節點間也是需要交流溝通的,這樣才能進行數據同步,那么直接把賬密寫入配置文件嗎?不,mongodb選擇創建一個keyfile實現集群間的權限驗證以便進行信息交互。
keyfile的內容可以是字符,但是保險起見一般使用工具生成:
openssl rand -base64 756 > /etc/mongo-keyfile
chown mongod.mongod /etc/mongo-keyfile
chmod 400 /etc/mongo-keyfile
--keyfile的權限只能是400,過高會導致節點無法啟動
然后將keyfile拷貝至副本集各個節點,並在配置文件中開啟keyFile選項即可。
需要特別提醒的是集群環境下如果開啟賬戶密碼驗證,那么只配置keyfile也是可以的,因為keyFile配置項默認會開啟authorization。官網原話是:keyFile implies security.authorization. 即keyfile項意味着auth同時開啟。