MongoDB 4.x安全管理
1、MongoDB如何鉴权
1.1、初体验
保证数据的安全性是数据库的重大职责之一。与大多数数据库一样,MongoDB内部提供了一套完整的权限防护机制。为了产生初步的认识,我们先用一个案例来说明。
打开mongo shell,连接MongoDB数据库,代码如下:

目标数据库开启了权限检查,这里由于提供了错误的用户名和密码,登录失败了。接着,使用正确的用户名和密码,再登录一次,并执行操作,代码如下:

这一次尽管登录成功了,但在对otherdb执行db.stats命令查看数据库状态时返回了Unauthorized错误。而提示中也说明了当前的操作并没有获得许可。切换到otherdb所属用户,再次进行鉴权,代码如下:

可以发现,在通过验明身份之后,对otherdb操作的鉴权获得了许可。
1.2、理解身份认证与授权
在上面的案例中不难发现,MongoDB对权限的过程主要涉及两个关键词:Authentication和Authorization。尽管大多数人对这两个词并不陌生,但是在理解上却很容易将它们混淆,具体的区别如下。
Authentication指认证,也被称为鉴权,一般是对用户身份的确认,也用于验证用户是否拥有访问系统的权利。Authorization指授权,对用户的授权决定了其是否可以对某些资源执行操作。
举个例子,乘客在高铁站入站时,需要同时提供身份证和车票(票证合一)。其中身份证用于识别乘客的身份,这是Authentication;而车票则用于检查乘客是否具有车次的乘坐权限,这是Authorization。
如果对MongoDB启用了访问控制,那么数据库会要求所有的客户端在访问之前通过身份验证。过程如图所示。

MongoDB的每一个用户都归属于某个数据库,用户需要在所属的数据库中进行鉴权。而一旦通过鉴权,当前会话(连接)中的所有操作将按照用户被赋予的角色权限执行检查。
1.3、身份认证方式
当前MongoDB支持的认证方式主要如下。
SCRAM(Salted Challenge ResponseAuthentication Mechanism):一种“挑战—应答”的鉴权机制,由IETF标准(RFC 5802)定义。MongoDB默认使用SCRAM鉴权方式,目前支持SCRAM-SHA-1、SCRAM-SHA-256两种算法,SCRAM-SHA-256在MongoDB 4.0版本开始支持,其具备更好的安全性。MongoDB Challenge and Response(MONGODB-CR):MongoDB 3.0版本以前采用的机制,MongoDB 4.0版本已经废弃。x.509 Certificate Authentication:基于证书的鉴权,采用该方式可建立SSL/TLS加密连接。LDAP proxy authentication:基于LDAP系统的鉴权,仅企业版支持。Kerberos authentication:基于Kerberos的鉴权,仅企业版支持。
1.3.1、SCRAM算法
SCRAM算法是当前推荐的鉴权方式,其交互流程如图所示。

步骤解读:
- 客户端发起一个SCRAM鉴权请求。在鉴权参数中加入用户名、客户端随机字符串(用于防止重放攻击)。
- 服务器端发出一个挑战响应。服务侧先检查用户名,通过后返回salt因子、迭代数、合并字符串(含客户端随机串和服务端随机串)。
- 客户端响应一个proof(证明数据)和合并字符串。响应的proof数据根据所给的随机参数以及客户端密钥生成,是一个客户端签名与密钥异或计算后的结果。
- 服务器端将存储的密钥结合随机参数,使用同样的算法生成签名并校验客户端的proof数据。若校验通过,服务器端采用类似方式发送自己的签名。
- 客户端校验服务器端签名数据。
MongoDB服务器端会为每个用户生成SCRAM验证所需的4个参数。
salt:密码加密使用的盐值,提供随机性保证。iterationCount:密码加密的迭代次数。storedKey:用于验证客户端的密钥。serverKey:用于验证服务器端的密钥。
使用db.getUser命令可以查看这些参数,代码如下:

SCRAM鉴权时有些类似SSL/TLS的握手过程,但相比之下简单许多,同时在性能方面也要具备优势。其对安全性的保证包括以下几点。
信息窃听:传输过程中全部采用动态签名,保证密码不会被传输。重放攻击:由于使用了随机数,每次生成的数据都不一样,可以避免重复数据的攻击。服务假冒:鉴权过程是双向的,即客户端会校验服务器端的身份,而服务器端密钥也根据密码生成,中间人无法仿造。存储安全:密码在数据库中均没有明文存储,都通过不可逆的算法加密存储。
1.3.2、内部认证
内部认证(鉴权)是指MongoDB集群内部节点之间进行访问的认证方式,比如副本集内主备节点之间的访问、分片集群内mongos与mongod之间的访问。内部认证支持两种方式。
(1)KeyFiles:密钥文件方式,采用SCAM的鉴权机制,文件内包含了一个共享密钥,由集群内所有成员共同持有。通常,密钥的长度在6~1024字符内,采用Base64编码。
(2)X.509证书:证书鉴权,用于SSL/TLS加密连接通道。
在分片集群中,由mongos统一负责对客户端进行认证,而整个集群的用户信息则存储在Config Server上。
1.4、RBAC访问控制
MongoDB使用了基于角色的访问控制模式——RBAC(Role Based Access Control),一组RBAC实体的示意如图所示。

先解释一下图中的几个实体。
Resource(资源):一个资源可以是一个数据库、集合或者一个集群。往大了说,任何可能被操作的事物都可以被当作资源。Action(动作):动作是指对资源的一种执行行为,比如读取表、读取数据库,其中读取便是一个动作。Privilege(权限):权限指的是对某类或某一些资源执行某些动作的允许,与Permission的意义一致。Role(角色):系统中的角色,通常代表了一种权力等级,比如论坛中的管理员、版主、游客等,就是角色;系统定义中,角色往往代表一组权限的集合。User(用户):可登录系统的实体,一个用户通常可被赋予多个角色。
简单的解释就是,权限定义了对某些资源的某些操作,而角色则可以拥有多个权限。例如用户可以被赋予多个角色,从而获得这些角色所拥有的权限,并用于操作某些资源。
MongoDB预制了大量的内部角色,可以满足绝大多数的使用场景。与此同时,你也可以创建自定义角色,并针对某些资源的特定操作进行授权。除了为用户进行授权,还要求在启动时指定–auth选项为MongoDB开启访问权限控制。
执行如下命令开启权限控制:
./bin/mongod --auth
此外,也可以通过配置文件指定security.authorization=true 来开启验证。
2、角色管理
2.1、角色管理命令
语法:
db.createUser({
user: "<用户名>",
pwd: "<密码>",
roles: [
{
role: "<角色名>",
db: "<数据库名>"
}
],
mechanisms: [ "<SCRAM-SHA-1 | SCRAM-SHA-256>" ],
passwordDigestor: "<server | client>"
})
roles 的两种写法:
- 写法一:指定数据库(推荐)
roles: [
{ role: "readWrite", db: "test" }
]
- 写法二:内置角色简写(当前数据库)
roles: ["readWrite", "dbAdmin"]
下面是一些具体的角色操作实例。
(1)创建集群管理员用户。
> use admin
> db.createUser({
user: 'admin',
pwd: 'adminpass',
roles: [
{role: 'clusterAdmin', db: 'admin'},
{role: 'userAdminAnyDatabase', db: 'admin'}
]
})
Successfully added user: {
"user" : "admin",
"roles" : [
{
"role" : "clusterAdmin",
"db" : "admin"
},
{
"role" : "userAdminAnyDatabase",
"db" : "admin"
}
]
}
(2)创建管理用户。
> use appdb
> db.createUser({
user: 'appuser',
pwd: 'apppass',
roles: ['dbAdmin']
})
Successfully added user: { "user" : "appuser", "roles" : [ "dbAdmin" ] }
(3)为用户授予数据库的读写权限角色。
> use appdb
> db.grantRolesToUser("appuser", [{role: 'readWrite', db: 'appdb'}])
(4)删除用户的角色。
> use appdb
> db.revokeRolesFromUser("appuser", [{role: "read", db: "appdb"}])
(5)查看用户。
> show users
{
"_id" : "appdb.appuser",
"userId" : UUID("2b51fc35-e96c-4933-8d06-17efdfd87b88"),
"user" : "appuser",
"db" : "appdb",
"roles" : [
{
"role" : "readWrite",
"db" : "appdb"
},
{
"role" : "dbAdmin",
"db" : "appdb"
}
],
"mechanisms" : [
"SCRAM-SHA-1",
"SCRAM-SHA-256"
]
}
//或
> db.getUsers()
MongoDB的用户及角色信息一般位于当前实例的admin数据库中,system.users集合中存放了所有数据。一种例外的情况是分片集群,应用接入mongos节点,鉴权数据则存放于config节点。因此有时为了方便分片集群管理,会单独为分片内部节点创建独立的管理操作用户。
2.2、系统内置角色
(1)数据库访问

(2)数据库管理

(3)集群管理

(4)备份恢复

(5)数据库通用角色

(6)特殊角色

2.3、创建自定义角色
2.3.1、基本使用
完整语法:
db.createRole({
role: "<角色名>",
privileges: [
{
resource: {
db: "<数据库名>",
collection: "<集合名>" // 或 "" 表示所有集合
},
actions: ["<操作1>", "<操作2>", ...]
}
],
roles: [
{
role: "<继承的角色名>",
db: "<角色所在数据库>"
}
],
authenticationRestrictions: [
{
clientSource: ["<IP/CIDR>"],
serverAddress: ["<IP/CIDR>"]
}
]
})
resource(资源)写法大全:
- 指定库 + 指定集合
resource: { db: "appdb", collection: "orders" }
- 指定库 + 所有集合
resource: { db: "appdb", collection: "" }
- 所有库 + 所有集合(慎用)
resource: { db: "", collection: "" }
- 指定集合前缀(MongoDB 4.0+)
resource: { db: "appdb", collection: "log_*" }
actions(操作)常用清单:
- CRUD
"find", "insert", "update", "remove"
- 索引
"createIndex", "dropIndex", "viewIndex"
- 集合管理
"createCollection", "dropCollection", "collMod"
- 聚合
"aggregate", "lookup"
管理类(谨慎)
"grantRole", "revokeRole", "changeOwnPassword"
- 官方完整列表:
https://www.mongodb.com/docs/manual/reference/privilege-actions/
使用createRole命令可以创建自定义角色,每一个角色都需要被绑定到指定的库中。普通的业务库中的角色对象只允许访问当前库的资源对象,而位于admin库的角色则没有此限制。我们定义了一个特殊的角色,用来对分散在多个业务库中的数据进行ETL处理,代码如下:
> use admin
> db.createRole(
{
role: "etlRole",
privileges: [
{resource: {db: "tracedb", collection: "etlLogs"}, actions: ["find", "update", "insert", "remove"]}
],
roles: [
{role: "read", db: "orderdb"},
{role: "read", db: "goodsdb"},
{role: "read", db: "userdb"}
]
},
{w: "majority", wtimeout: 5000}
)
{
"role" : "etlRole",
"privileges" : [
{
"resource" : {
"db" : "tracedb",
"collection" : "etlLogs"
},
"actions" : [
"find",
"update",
"insert",
"remove"
]
}
],
"roles" : [
{
"role" : "read",
"db" : "orderdb"
},
{
"role" : "read",
"db" : "goodsdb"
},
{
"role" : "read",
"db" : "userdb"
}
]
}
//查看角色
> db.getRoles()
这里的etlRole支持的权限包括:
- orderdb、goodsdb、userdb数据库的read角色的权限。
- tracedb数据库中etlLogs集合的读写权限。
下一步是为用户授予自定义角色,注意etlRole位于admin数据库中(具备跨库访问的功能),代码如下:
> use somedb
> db.grantRolesToUser("someone", [{role: 'etlRole', db: 'admini'}])
2.3.2、经典实例
示例 1:只允许读写某一张表(最常用)
use appdb
db.createRole({
role: "orderReader",
privileges: [
{
resource: { db: "appdb", collection: "orders" },
actions: ["find", "insert", "update"]
}
],
roles: []
})
//然后给用户绑定:
db.grantRolesToUser("appuser", ["orderReader"])
示例 2:业务账号(禁止删库)
db.createRole({
role: "appRole",
privileges: [
{
resource: { db: "appdb", collection: "" },
actions: [
"find",
"insert",
"update",
"remove",
"createIndex"
]
}
],
roles: []
})
示例 3:只读角色(跨库)
db.createRole({
role: "crossDbRead",
privileges: [
{
resource: { db: "db1", collection: "" },
actions: ["find"]
},
{
resource: { db: "db2", collection: "" },
actions: ["find"]
}
],
roles: []
})
示例 4:继承内置角色
db.createRole({
role: "monitorRole",
privileges: [],
roles: [
{ role: "read", db: "appdb" },
{ role: "clusterMonitor", db: "admin" }
]
})
示例 5:限制 IP 登录(企业级)
db.createRole({
role: "ipRestrictedRole",
privileges: [
{
resource: { db: "appdb", collection: "" },
actions: ["find"]
}
],
roles: [],
authenticationRestrictions: [
{
clientSource: ["10.0.0.0/8"],
serverAddress: ["192.168.1.100"]
}
]
})
查看角色
show roles // 当前数据库
db.getRole("orderReader")
db.getRole("orderReader", { showPrivileges: true })
删除角色
db.dropRole("orderReader")
修改角色
db.updateRole("orderReader", {
privileges: [
{
resource: { db: "appdb", collection: "orders" },
actions: ["find", "insert"]
}
]
})
3、最小权限原则
为了保证数据不会被随意地越权访问,最好的实践是遵循最小权限原则。
- 每一个用户应该拥有完成任务所需的最少角色。
- 每一个(自定义)角色应该拥有最少的资源操作权限,避免被提前、过多地分配。
- 建立用户访问权限资料库,评审并记录每一次变更,执行定期的审视。
- 用户权限一旦不再需要,应该立即收回。
一般来说,对于数据库的每个应用(微服务)来说,至少考虑逻辑库级别的权限隔离方式。例如,为订单服务使用orderdb数据库,并创建一个新的MongoDB用户orderuser,为该用户赋予orderdb的数据读写权限。除此之外,不应该为orderuser增加任意其他的权限,而其他微服务也是如此,微服务之间不允许跨库访问,如图所示。

这是一种典型的场景,将其作为微服务集群线上配置的基本模式也无可厚非。然而,变数一直都会存在。例如,希望对已有的数据库执行ETL处理,以便满足数据挖掘的目的,又或者是因微服务架构变更所产生的对数据库的操作需求。这些情况可能会打破微服务之间数据权限隔离的基本模式,并导致数据库角色产生越权的风险。
为此,我们应该对一些“高级别”的角色或者权限保持谨慎的态度,下面进行介绍。
3.1、存在风险的角色
root是一个超级用户角色,其几乎所有的操作都会获得通过。这是一个危险的权限,最好的方法是细化权限的需求,尽量使用一个非root账号进行操作。userAdminAnyDatabase允许你为任意数据库执行用户管理,包括创建任意权限的用户。而且userAdminAnyDatabase角色没有限制用户可以授予的权限,这意味着拥有该角色的用户可以授予它们自己比现在更多的权限,因此userAdminAnyDatabase也是一个典型的超级用户角色。userAdmin允许用户在当前数据库中为自己赋予更高的权限,在业务应用中应避免使用。而且,如果userAdmin被绑定到admin数据库,那么将可以创建对任意数据库任意读写和管理的权限。__system是一个内部角色,用于分片集群、副本集成员之间的认证,这个角色会绕过所有的权限检查,应禁止使用。readAnyDatabase允许你对任意数据库进行读取,可能存在越权的风险。readWriteAnyDatabase允许你对任意数据库进行读取、写入,可能存在越权的风险。dbOwner整合了readWrite、dbAdmin和userAdmin的权限,不利于权限管理。backup、restore允许对全局数据进行备份、替换的权限,应该在有限的场景中使用。
执行如下命令,可以检视当前的用户角色:
> use admin
> db.system.users.find({}, {"roles.fole": 1})
{ "_id" : "admin.admin", "roles" : [ { }, { } ] }
{ "_id" : "appdb.appuser", "roles" : [ { }, { } ] }
3.2、存在风险的动作
- 对于定义了
anyAction动作权限的角色,该角色用户便拥有对某个资源的任何操作,不利于权限的管理。 internal与anyAction类似,拥有internal角色的用户拥有对任意资源的任意操作权限,非常不利于权限的管理。createRole、createUser、grantRole等一系列动作允许在数据库中创建任意的角色、用户,或者执行任意的授权动作,这些都可能导致越权。changePassword允许对数据库的任意用户修改密码,属于高风险行为。closeAllDatabases、shutdown允许关闭数据库并释放内存,然后中止进程,可能导致意外的业务中断。- 对于定义了
dropDatabase动作权限的角色,该角色用户可执行dropDatabase命令删除任意的数据库,一些恶意操作可能会直接导致业务数据丢失。 getParameter、setParameter允许对当前数据库集群的内部参数进行“窥探”,其中setParameter还会对数据库行为产生更改,不当的设置可能会影响数据库的正常运行。getCmdLineOpts允许获得数据库启动的命令行参数,可能导致内部配置泄露,例如以-p附带的密码信息。尽量将配置信息写入配置文件,可以降低一些风险。
执行如下命令,检视是否存在风险动作权限的角色:
> use admin
> db.system.roles.find({"privileges.actions": "createRole"}, {"roles.role": 1})
4、安全最佳实践
4.1、安全认证
- 在产品部署上始终开启安全认证,保证远程主机连接的数据库身份是合法的。检查你的配置文件,将security.authorization选项设置为true。对于命令行启动的mongod进程,必须使用–auth选项。即便在可信的网络中部署MongoDB服务器,启用–auth选项也是必要的,因为当你的网络受到攻击时它能够提供“深层防御”。
- 为数据库用户设置一个复杂的密码,避免使用过于简单的密码。
- 避免使用常用的用户名,如mongodba、root、user等,因为它们是最容易猜到的。
- 移除默认的test数据库。
- 避免明文密码的泄露,在命令行中使用–password{real pass}会导致密码可视化,在shell脚本中使用明文密码同样存在泄露风险。建议使用passwordPrompt命令(MongoDB 4.2版本提供)实现交互式方式输入。
- 在完成首次搭建之后,应该立刻禁用enableLocalhostAuthBypass选项,这是一种本地例外的登录方式(local exception)。在没有建立任何账号时,需要使用本地例外方式登录,而建立管理员账号后,要及时关闭本地例外认证方式。
4.2、权限管理
- 始终从创建管理员用户开始,然后根据具体的需要添加其他用户。
- 始终遵循最小化权限原则,在理解每个细节的前提下,执行权限的细粒度控制。
- 考虑实现应用级别的隔离,避免应用产生越权。
- 仔细审视超级用户的合法性及机密性,同时避免存在未知的用户角色。
4.3、网络配置
- 避免默认端口:MongoDB的默认端口号是27017,使用默认端口容易被监听,存在安全隐患,建议使用非默认端口。使用–port指定监听端口。
- 禁用HTTP接口:对于MongoDB 3.4或以下版本,设置net.http.enabled为false以禁用HTTP接口。从MongoDB 3.6版本开始,该功能已经被废弃。
- 配置绑定IP:如果系统存在多个网络接口,则应该使用net.bindIp选项限制MongoDB监听的IP地址。默认情况下MongoDB绑定所有的接口(0.0.0.0),这是不推荐的。
- 限制网络访问:尽可能在可信的网络中运行数据库,应该将MongoDB部署在内部网络,通过防火墙或安全组来限制访问。一般情况下,业务服务通过内部网络访问MongoDB,如图所示。

如果需要通过指定的外部网络访问集群内的MognoDB,例如跨Region的访问,建议使用VPN保证数据传输的安全性。与此同时,还可以为MongoDB运行环境设定IP白名单,避免非法的客户端访问。
- 使用TLS/SSL。默认情况下,MongoDB客户端和服务器端之间的数据传输是明文的,存在被窃听、篡改的风险。需要进行一些风险评估来使用TLS/SSL功能,例如通过互联网访问MongoDB时就必须使用TLS/SSL功能。而且,基于TLS/SSL的业务不应该使用弱安全等级的加密算法,所有连接使用的密钥长度不应该小于128位。
如果业务客户端使用了TLS/SSL加密连接,还应该避免sslAllowInvalidCertificates和allowInvalidHostnames这样的选项。客户端始终应该对服务器证书、名称进行验证,这可以避免遭受“中间人”攻击。在一些安全级别要求更高的情况下,在MongoDB服务器端将net.tls.allowConnectionsWithoutCertificates设置为false,可以要求客户端在TLS/SSL握手阶段提供合法的证书,进一步避免身份被冒充。
MongoDB集群内部可以使用keyFile作为认证方式,但数据传输是明文方式。如果有更高的安全需求,还可以考虑在集群内部启用TLS/SSL方式。
4.4、文件安全
- 使用单独的操作系统用户运行MongoDB,该用户除了用于运行数据库,不应该有任何其他权限。使用root运行数据库会为系统带来不必要的风险。
- MongoDB的安装目录${MONGODB_HOME}应该设置一定的权限,避免未认证的访问,代码如下:
chown mongouser:mongogroup ${MONGODB_HOME}
chmod 0700 ${MONGODB HOME}
${MONGODB_HOME}/bin中包含了二进制程序。如果需要额外的运维操作,则可将bin目录及需要执行的二进制文件设置为0750权限。对于配置文件,可设置为0600以保证无可执行权限,代码如下:
chmod 0600 ${MONGODB_HOME}/mongo.conf
${MONGODB_HOME}/bin中包含了二进制程序。如果需要额外的运维操作,则可将bin目录及需要执行的二进制文件设置为0750权限。对于配置文件,可设置为0600以保证无可执行权限,代码如下:
chmod 0600 ${MONGODB_HOME}/mongo.conf
除此之外,mongo.conf可能包含许多系统配置,可以定期校验文件的哈希值,保护文件不受未授权的更改。
- 限制MongoDB数据、日志文件目录的权限。
对于数据目录${MONGODB_DATA},设置如下:
chown mongouser:mongogroup ${MONGODB_DATA}
chmod 0700 ${MONGODB_DATA}
对于日志文件${MONGODB_LOGFILE},设置如下:
chown mongouser:mongogroup ${MONGODB_LOGFILE}
chmod 0600 ${MONGODB_LOGFILE}
- 对于更高安全级别的场景,可使用文件级的加密。如果使用的是MongoDB企业版,则可以使用服务器端加密(encryption at rest)的特性来实现本地文件的加密。
4.5、日志记录
- 对部署的MongoDB开启日志记录,保持对数据库行为的跟踪。生产环境不可以使用-quiet或者将systemLog.quiet设置为true,由于该模式下会限制输出信息(数据库命令输出,副本集活动,连接接收事件,连接关闭事件),因此不利于问题的跟踪排查。
- 设置合理的日志级别,verbosity等级决定了日志的输出明细。verbosity默认值是0,表示info级别;1~5表示debug级别,并逐步细化调试信息的输出。
- 采用追加式日志输出,而不是覆盖。设定systemLog.logAppend=true,当进程重新启动后,该选项可确保MongoDB追加新的条目到日志文件的末尾,而不是重写日志内容。
- 使用审计功能。审计功能可以用来记录用户对数据库的所有相关操作。这些记录可以让系统管理员在任何时候分析数据库发生的一些行为。注意:MongoDB企业版支持审计功能,社区版不支持审计功能。
4.6、禁用不安全的功能
- 关闭服务器端的脚本运行功能。MongoDB允许在服务器端内部执行部分JavaScript脚本代码,例如$where查询操作,以及mapReduce、group命令。如果不是必须的情况,则建议关闭该功能,将security.javascriptEnabled设置为false,或使用–noscripting选项启动。
关闭服务器端脚本支持并不影响在mongo shell中使用脚本。由于该功能存在一些命令注入的风险,所以最好不要启用。
- 启用net.wireObjectCheck选项,用于检查插入数据的有效性,默认值为true。开启该选项后,MongoDB在收到请求时会先进行校验,拒绝畸形或无效的BSON数据写入。
4.7、加强安全管理
- 选择安全稳定的MongoDB版本。需要对现网运行的版本进行评估,对于官方已经不再维护或存在重大漏洞的版本,应尽早升级。
- 使用配置管理软件进行数据库管理,提升效率。
- 审视数据安全级别,考虑在应用层实施加密。
- 定期对数据库系统的安全性进行复盘,审视系统用户角色权限、网络配置等是否合理。关注MongoDB安全动态,并周期性地执行安全补丁更新。

1601

被折叠的 条评论
为什么被折叠?



