数据库安全涉及到两个方面,一是身份认证,二是鉴权。
mongodb安装完后默认是不开启auth安全模块的,直接通过 mongo
工具或者其他工具不需要身份认证就可以成功连接mongo服务。
1 开启安全模式 --auth
为了安全起见,我们需要开启安全模式 --auth
[root@docker01 ~]# /usr/local/mongodb-4.4.3/bin/mongod -f /usr/local/mongodb-4.4.3/mongodb.conf --auth
about to fork child process, waiting until server is ready for connections.
forked process: 4195
child process started successfully, parent exiting
[root@docker01 ~]#
这时,我们连接来读取 test 数据库的集合,发现读取失败。
[root@docker01 ~]# /usr/local/mongodb-4.4.3/bin/mongo --host localhost --port 27017
MongoDB shell version v4.4.3
connecting to: mongodb://localhost:27017/?compressors=disabled&gssapiServiceName=mongodb
Implicit session: session { "id" : UUID("5106e9d5-4480-414b-b0b5-6c6b729028ab") }
MongoDB server version: 4.4.3
> use test
switched to db test
> show collections
Warning: unable to run listCollections, attempting to approximate collection names by parsing connectionStatus
>
MongoDB将所有角色信息存储在admin数据库的admin.system.roles集合中。一般,切换到admin数据库,创建角色,添加用户。
创建一个管理员用户 root,密码也是 root(创建用户细节下面详解)。
> use admin
switched to db admin
> db.createUser({ user: "root", pwd: "root", roles: [{ role: "userAdminAnyDatabase", db: "admin" }] })
Successfully added user: {
"user" : "root",
"roles" : [
{
"role" : "userAdminAnyDatabase",
"db" : "admin"
}
]
}
>
1.1 身份认证
方式一:mongo -u <username> -p <password> --authenticationDatabase <db>
身份认证后,可以查看 test 数据库的集合
[root@docker01 ~]# /usr/local/mongodb-4.4.3/bin/mongo --host localhost --port 27017 -u root -p root --authenticationDatabase admin
MongoDB shell version v4.4.3
connecting to: mongodb://localhost:27017/?authSource=admin&compressors=disabled&gssapiServiceName=mongodb
Implicit session: session { "id" : UUID("89008f1d-7278-4387-9992-7ff1054a7d3f") }
MongoDB server version: 4.4.3
> use test
switched to db test
> show collections
outDoc
user
>
方式二:
不指定用户名密码连接上mongo,然后使用 db.auth("username","password")
;
[root@docker01 ~]# /usr/local/mongodb-4.4.3/bin/mongo --host localhost --port 27017
MongoDB shell version v4.4.3
connecting to: mongodb://localhost:27017/?compressors=disabled&gssapiServiceName=mongodb
Implicit session: session { "id" : UUID("3736b07f-4793-4dcb-8af8-ca48d9f508f7") }
MongoDB server version: 4.4.3
> use admin
switched to db admin
> db.auth("root","root")
1
> show collections
system.users
system.version
> show databases
admin 0.000GB
config 0.000GB
local 0.000GB
test 0.000GB
>
2. 权限和内置角色
2.1 权限
MongoDB的权限包由:资源(Resource)和操作(Action)两部分组成。
权限:在哪里+做什么
资源:
- 集群级别:
{ cluster: true }
; - 数据库级别:
{ db: "test", collection: "" }
; - 表级别:
{ db: "test", collection: "user" }
;
Privilege Actions 定义User能够在资源上执行的操作。
例如:MongoDB在文档级别上执行的读写操作列表是:find,insert,remove,update
;
2.2 mongo 内置的角色:
- 普通数据库用户的角色:
- read:只读数据的权限
- readWrite:读写数据的权限
- 跨库角色:除config和local之外所有的数据库:
- readAnyDatabase:在所有数据库上读取数据的权限
- readWriteAnyDatabase:在所有数据库上读写数据的权限
- userAdminAnyDatabase:在所有数据库上管理User的权限
- dbAdminAnyDatabase:管理所有数据库的权限
- 数据库管理角色:
- userAdmin:在当前DB中管理User
- dbAdmin:在当前dB中执行管理操作
- dbOwner:在当前DB中执行任意操作
- 超级角色:
- userAdmin
- userAdminAnyDatabase
- root
- 集群管理角色:
- clusterMonitor:授予监控集群的权限,对监控工具具有readonly的权限
- clusterAdmin:授予管理集群的最高权限
- clusterManager:授予管理和监控集群的权限
- hostManager:管理Server
- 备份恢复角色:
- backup
- restore
- 内部角色:
- __system
- __queryableBackup
- 分片角色:
- enableSharding
3. 角色
3.1 创建角色
db.createRole(<role>, <writeConcern>)
<role>
: 角色定义文档<writeConcern>
: 写安全级别
在创建角色时,必须明确Role的四个特性:
Scope
:角色作用的范围,
- 创建在Admin中的角色,能够在其他DB中使用;在其他DB中创建的角色,只能在当前DB中使用;
- 在admin 数据库中创建的角色,Scope是全局的,能够在admin,其他DB和集群中使用,并且能够继承其他DB的Role;而在非admin中创建的角色,Scope是当前数据库,只能在当前DB中使用,只能继承当前数据库的角色。
因此创建角色,一般都在admin库中创建。
Resource
:角色控制的资源,表示授予在该资源上执行特定操作的权限;Privilege Actions
:定义了User能够在资源上执行的操作,系统定义Action是:Privilege Actions;Inherit
:角色能够继承其他角色权限;
role文档格式如下:
{
role: "<name>", //角色名
privileges: [ //权限数组,包括资源和权限操作
{ resource: { <resource> }, actions: [ "<action>", ... ] },
...
],
roles: [//父类角色数组,对于该数据库角色可直接用role字符串表示
{ role: "<role>", db: "<database>" } | "<role>",
...
],
authenticationRestrictions: [ //认证限制数组,可选,确定一组可连接IP地址、CIDR范围
{
clientSource: ["<IP>" | "<CIDR range>", ...],
serverAddress: ["<IP>" | "<CIDR range>", ...]
},
...
]
}
创建一个针对 test 数据库user集合的只读角色 testRead
> use admin
> db.createRole(
... {
... role: "testRead",
... privileges: [
... { resource: { db: "test", collection: "user" }, actions: [ "find", "listCollections","listIndexes"] }
... ],
... roles: []
... }
... );
{
"role" : "testRead",
"privileges" : [
{
"resource" : {
"db" : "test",
"collection" : "user"
},
"actions" : [
"find",
"listCollections",
"listIndexes"
]
}
],
"roles" : [ ]
}
>
3.2 查看角色
查看所有角色
show roles
> show roles
{
"role" : "__queryableBackup",
"db" : "admin",
"isBuiltin" : true,
"roles" : [ ],
"inheritedRoles" : [ ]
}
...
...
>
查看具体的角色
db.getRole(<RoleName>)
查看内置的 read 角色
> db.getRole("read")
{
"role" : "read",
"db" : "admin",
"isBuiltin" : true,
"roles" : [ ],
"inheritedRoles" : [ ]
}
>
查看上面创建的 testRead 角色的具体信息 {showPrivileges: true}
> db.getRole("testRead",{showPrivileges: true})
{
"role" : "testRead",
"db" : "admin",
"isBuiltin" : false,
"roles" : [ ],
"inheritedRoles" : [ ],
"privileges" : [
{
"resource" : {
"db" : "test",
"collection" : "user"
},
"actions" : [
"find",
"listCollections",
"listIndexes"
]
}
],
"inheritedPrivileges" : [
{
"resource" : {
"db" : "test",
"collection" : "user"
},
"actions" : [
"find",
"listCollections",
"listIndexes"
]
}
]
}
>
3.3 修改角色
db.updateRole(<roleName>, <roleDocument>)
将 testRead 角色修改为针对整个test数据库只读
> db.updateRole("testRead",
... {
... privileges: [
... { resource: { db: "test", collection: "" }, actions: [ "find", "listCollections","listIndexes"] }
... ],
... roles: []
... }
... );
>
查看修改后 testRead 角色的详细信息
> db.getRole("testRead",{showPrivileges: true})
{
"role" : "testRead",
"db" : "admin",
"isBuiltin" : false,
"roles" : [ ],
"inheritedRoles" : [ ],
"privileges" : [
{
"resource" : {
"db" : "test",
"collection" : ""
},
"actions" : [
"find",
"listCollections",
"listIndexes"
]
}
],
"inheritedPrivileges" : [
{
"resource" : {
"db" : "test",
"collection" : ""
},
"actions" : [
"find",
"listCollections",
"listIndexes"
]
}
]
}
>
3.4 删除角色
db.dropRole("roleName")
创建test数据库的写角色 testWrite,然后删除 testWrite 角色,再次查看,返回null,表示角色不存在
#使用管理员 root 用户进行操作
> db.auth("root","root")
1
# 创建角色 testWrite
> db.createRole(
... {
... role: "testWrite",
... privileges: [
... { resource: { db: "test", collection: "" }, actions: [ "insert", "update","remove"] }
... ],
... roles: []
... }
... );
{
"role" : "testWrite",
"privileges" : [
{
"resource" : {
"db" : "test",
"collection" : ""
},
"actions" : [
"insert",
"update",
"remove"
]
}
],
"roles" : [ ]
}
# 查看 testWrite 角色
> db.getRole("testWrite")
{
"role" : "testWrite",
"db" : "admin",
"isBuiltin" : false,
"roles" : [ ],
"inheritedRoles" : [ ]
}
# 删除 testWrite 角色
> db.dropRole("testWrite")
true
# 再次查看,返回null,表示角色不存在
> db.getRole("testWrite")
null
>
4. 创建用户
db.createUser(<user>, <writeConcern>)
<user>
: 验证和访问信息<writeConcern>
: 安全写级别
user文档格式如下:
{
user: "<name>", //用户名
pwd: "<cleartext password>", //用户密码
customData: { <any information> }, //备注信息,可选
roles: [//角色数组,授权给此用户的角色,空数组[]表示无角色
{ role: "<role>", db: "<database>" } | "<role>",
...
],
authenticationRestrictions: [//限制数组,可选
{
clientSource: ["<IP>" | "<CIDR range>", ...]
serverAddress: ["<IP>" | "<CIDR range>", ...]
},
...
],
mechanisms: [ "<SCRAM-SHA-1|SCRAM-SHA-256>", ... ], //指定用于创建SCRAM用户凭据的特定SCRAM机制,可选。3.6默认SCRAM-SHA-1
passwordDigestor: "<server|client>" //密码摘要,可选,指定用户端/服务器是否生成密码摘要
}
使用上面的 testRead角色,创建一个针对test数据库的只读用户, 名字叫做 testRead,密码也是 testRead。
> user admin
> db.createUser({ user: "testRead", pwd: "testRead", roles: [{ role: "testRead", db: "admin" }] })
Successfully added user: {
"user" : "testRead",
"roles" : [
{
"role" : "testRead",
"db" : "admin"
}
]
}
对上面创建的 testRead 用户进行身份认证,然后查看test数据库集合,查看user集合内容。因为只有读权限,所以插入文档时,报错"Unauthorized",未授权。
# 身份认证
> db.auth("testRead","testRead")
1
> use test
switched to db test
# 查看数据库中的集合
> show collections
outDoc
user
# 可以查看user集合内容
> db.user.find()
{ "_id" : "1", "money" : 1500, "name" : "刘一一", "gender" : "女" }
{ "_id" : "2", "money" : 1000, "name" : "陈二", "gender" : "女" }
{ "_id" : "3", "name" : "张三", "sal" : 1000, "gender" : "女" }
{ "_id" : "4", "money" : 800, "name" : "李四", "gender" : "女" }
{ "_id" : "5", "money" : 1000, "name" : "王五", "sal" : 500, "gender" : "女" }
{ "_id" : "6", "name" : "赵六", "money" : 1000 }
{ "_id" : "7", "money" : 1000, "name" : "孙七", "gender" : "女" }
{ "_id" : ObjectId("5fff6a78025e5d9ea86e18cc"), "money" : 1000, "name" : "周八", "gender" : "女" }
{ "_id" : { "age" : 18, "gender" : "男" }, "money" : 1000, "name" : "吴九", "gender" : "女" }
{ "_id" : ObjectId("5fff95783f6d5ed2fcce4133"), "money" : 2000, "name" : "ZHANG", "addr" : [ "三亚", "北京" ], "gender" : "女" }
{ "_id" : ObjectId("60045f5719414c7b5be4c122"), "name" : "富二代", "money" : 1500 }
{ "_id" : ObjectId("6004611c19414c7b5be4c13f"), "name" : "打工人", "money" : 800 }
{ "_id" : ObjectId("6004c82e21bca04a14f52bee"), "money" : 1100 }
{ "_id" : ObjectId("6004ce5521bca04a14f52bf0"), "money" : 1200 }
{ "_id" : ObjectId("6004ce8f21bca04a14f52bf1"), "money" : 1300, "name" : null }
# 插入文档失败,未授权
> db.user.insert({name:"只读用户"})
WriteCommandError({
"ok" : 0,
"errmsg" : "not authorized on test to execute command { insert: \"user\", ordered: true, lsid: { id: UUID(\"3736b07f-4793-4dcb-8af8-ca48d9f508f7\") }, $db: \"test\" }",
"code" : 13,
"codeName" : "Unauthorized"
})
>