在现代应用中,数据库往往承载着大量敏感的用户数据与业务信息。为了确保这些数据不被未授权的人员获取,必须为 MongoDB 集群构建多层次的安全防护体系。

在日常生活中,我们经常会遇到需要证明身份的情况。比如去银行取钱,你需要出示身份证和银行卡,这就是认证的过程。认证的目的是验证「你是谁」。而一旦确认了你的身份,银行会根据你的账户类型决定你可以进行哪些操作,比如普通账户可能不能进行大额转账,这就是授权的过程。
认证解决的是「你是谁」的问题,而授权解决的是「你可以做什么」的问题。两者相辅相成,缺一不可。
MongoDB 支持多种认证机制,以满足不同的业务和安全需求。对于社区版本,主要实现了基于 SCRAM 的口令认证以及基于 x.509 的证书认证。企业版进一步扩展,支持 Kerberos 票据认证和 LDAP 代理认证,便于与企业级身份管理系统集成。
这里我们将重点阐述 x.509 证书认证机制。该机制遵循公钥基础设施标准,通过数字证书实现实体身份与公钥的绑定。证书由受信任的证书颁发机构签发,确保公钥归属的准确性与安全性,有效防范中间人攻击和身份伪造,为敏感数据和通信过程提供专业的安全保障。
在 MongoDB 中创建用户时,需为其指定认证数据库(authentication database),该数据库与用户名联合唯一标识一个用户。值得注意的是,用户的权限并不局限于认证数据库本身,可以被授予访问其他数据库的能力,从而实现更灵活的权限设计。
MongoDB 提供了丰富的内置角色体系,覆盖不同的运维和开发场景,简化权限控制。下表列举了主要内置角色及其权限范围:
除上述内置角色外,MongoDB 允许创建自定义角色,通过组合所需操作权限,实现更为细粒度和符合业务实际需求的访问控制策略。
在实际生产环境中,MongoDB 集群一般由多个成员节点组成。为保障整个集群的安全性,集群各成员之间,以及成员与客户端之间的通信,都必须经过严格的身份认证。每个副本集成员需相互完成身份验证,客户端在访问主节点或从节点时同样也要通过认证。
采用 x.509 证书机制时,所有证书须由同一个权威且受信任的证书颁发机构(CA)签发。CA 作为核心信任锚点,有效防止中间人攻击等安全威胁。
下方示意图展示了三节点 MongoDB 副本集在 x.509 认证方案下的信任架构。可以直观看到,客户端、各副本集成员及 CA 之间的信任和认证关系。
成员和客户端证书具有相似的结构,以下是一个示例:
|证书: 数据: 版本:1 (0x0) 序列号:1 (0x1) 签名算法:sha256WithRSAEncryption 颁发者:C=CN, ST=GD, L=Shenzhen, O=MongoDB, CN=CA-SIGNER 有效期 Not Before: 2018年11月11日 22:00:03 GMT Not After : 2019年11月11日 22:00:03 GMT 主题:C=CN, ST=GD, L=Shenzhen, O=MongoDB, OU=MyServers, CN=server1 主题公钥信息: 公钥算法:rsaEncryption 公钥:(2048 bit
为了在 MongoDB 中使用 x.509 认证,成员证书必须满足以下条件:
在为集群成员和客户端生成证书之前,首先需配置证书颁发机构(CA)。可根据实际需求自建独立 CA 或采用第三方受信任的 TLS/SSL 证书颁发机构。我们的课程中为演示目的采用自建 CA,便于大家理解流程。但是在生产环境强烈建议使用权威第三方 CA 以确保安全性和合规性。
根 CA 是证书链的顶层,是最终的信任来源。在理想情况下,应该使用第三方 CA。但在隔离网络环境(如大型企业内部网络)或测试场景中,我们需要使用本地 CA。 首先我们初始化一些变量用于后续的证书生成:
|dn_prefix="/C=CN/ST=GD/L=Shenzhen/O=MongoDB" ou_member="MyServers" ou_client="MyClients" mongodb_server_hosts=("server1" "server2" "server3") mongodb_client_hosts=("client1" "client2") mongodb_port=27017
接下来创建密钥对并存储在根 CA 密钥文件中:
|# 在生产环境中,你需要为密钥设置密码保护 # openssl genrsa -aes256 -out root-ca.key 4096 openssl genrsa -out root-ca.key 4096
然后创建 OpenSSL 配置文件,用于生成证书:
|# CA 策略 [ policy_match ] countryName = match stateOrProvinceName = match organizationName = match organizationalUnitName = optional commonName = supplied emailAddress = optional [ req ] default_bits = 4096 default_keyfile = server-key.pem default_md = sha256 distinguished_name = req_dn req_extensions = v3_req
使用 openssl req 命令创建根证书。由于根 CA 是证书链的顶层,我们使用私钥自签名证书:
|openssl req -new -x509 -days 1826 -key root-ca.key -out root-ca.crt \ -config openssl.cnf -subj "$dn_prefix/CN=ROOTCA"
我们可以使用以下命令查看根证书的可读版本:
|openssl x509 -noout -text -in root-ca.crt
完成根 CA 的创建后,下一步我们将生成用于签发成员证书和客户端证书的中间 CA。中间 CA 是由根 CA 签名、授权用于实际签署服务器及客户端证书的证书。 这一层级结构符合安全领域的通行规范,可有效隔离根 CA,提升整体安全性。实际运维中,往往会针对不同用途(如服务器、客户端等)分别设立中间 CA,从而在个别中间 CA 泄露时,仅需撤销受影响分支的信任关系,无需重置整个信任体系。
|# 同样,在生产环境中你需要为签名密钥设置密码保护: # openssl genrsa -aes256 -out signing-ca.key 4096 openssl genrsa -out signing-ca.key 4096 openssl req -new -key signing-ca.key -out signing-ca.csr \ -config openssl.cnf -subj "$dn_prefix/CN=CA-SIGNER" openssl x509 -req -days 730 -in signing-ca.csr -CA root-ca.crt -CAkey \ root-ca.key -set_serial 01 -out signing-ca.crt -extfile openssl.cnf
上述过程中,我们先通过 openssl req 命令生成证书签名请求(CSR),然后利用 openssl ca(或 x509)命令,对该请求进行签名,完成中间 CA 证书的颁发。
完成签名 CA 创建后,需要将根证书(含根 CA 公钥)和签名 CA 证书(含中间 CA 公钥)合并为一个 PEM 文件。这个 PEM 文件后续会被 mongod 或客户端作为 --tlsCAFile 指定,作为整个证书链的信任依据。
|cat root-ca.crt > root-ca.pem cat signing-ca.crt >> root-ca.pem
成员证书(即 x.509 服务器证书)专为 mongod 和 mongos 进程设计,是实现 MongoDB 集群内成员身份认证的关键凭据。每个集群节点都会使用该证书,确保与其他副本集成员之间的通信受信任且安全。 通俗点理解就是每个 mongod 实例会通过自身的服务器证书,向集群里的其它成员“证明身份”。
在实际操作中,集群往往由多个成员组成,因此我们通常通过循环批量生成所有所需的成员证书。
|# 注意 openssl req 命令中主题的 OU 部分 for host in "${mongodb_server_hosts[@]}"; do echo "为 $host 生成密钥" openssl genrsa -out ${host}.key 4096 openssl req -new -key ${host}.key -out ${host}.csr -config openssl.cnf \ -subj "$dn_prefix/OU=$ou_member/CN=${host}"
每个证书涉及三个步骤:
注意变量 $ou_member。这标志着服务器证书和客户端证书的区别。服务器和客户端证书必须在主题 DN 的组织部分有所不同。更具体地说,它们必须在 O、OU 或 DC 值中的至少一个上有所不同。
客户端证书主要供 mongo shell、MongoDB Compass、各类官方工具及基于驱动的应用程序进行安全身份认证使用。 其生成流程与成员服务器证书高度相似,但需特别注意,客户端证书在主题 DN 中应采用 $ou_client 变量(通常位于 OU 字段),以与服务端证书在 O、OU 或 DC 等字段上形成明确区隔,确保安全隔离和身份唯一性。
|# 注意 openssl req 命令中主题的 OU 部分 for host in "${mongodb_client_hosts[@]}"; do echo "为 $host 生成密钥" openssl genrsa -out ${host}.key 4096 openssl req -new -key ${host}.key -out ${host}.csr -config openssl.cnf \ -subj "$dn_prefix/OU=$ou_client/CN=${host}"
在未启用认证的环境下,可按如下方式依次启动副本集所有成员节点。此操作沿用“生成根 CA”过程所用的相关变量,通过循环批量为每个 mongod 实例初始化运行环境,实现副本集基础服务的部署和启动。
|mport=$mongodb_port for host in "${mongodb_server_hosts[@]}"; do echo "以非认证模式启动服务器 $host" mkdir -p ./db/${host} mongod --replSet set509 --port $mport --dbpath ./db/$host \ --fork --logpath ./db/${host}.log let "mport++" done
一旦每个 mongod 启动,我们就可以使用这些 mongod 初始化副本集。
|myhostname=`hostname` cat > init_set.js <<EOF rs.initiate(); mport=$mongodb_port; mport++; rs.add("localhost:" + mport); mport++; rs.add("localhost:" + mport); EOF mongo localhost:$mongodb_port init_set.js
前面的脚本实际上是通过批量生成并执行命令,自动完成副本集的初始化工作:先将初始化和扩容命令写入 JavaScript 文件,然后借助 mongo shell 执行这些命令。执行流程为:首先连接到 27017 端口上的 mongod 实例,完成副本集的初始化,随后将其他两个 mongod(默认运行在 27018 和 27019 端口)依次加入到该副本集当中。
接下来,我们将以“生成和签署客户端证书”环节创建的某个客户端证书为基础,创建副本集的管理员用户。后续我们可以使用该用户,通过 mongo shell 或其它工具安全地访问并管理集群。
需要注意的是,x.509 证书认证机制要求必须将客户端证书的主题(Subject)作为用户名添加到 MongoDB 用户体系中,每个 x.509 客户端证书仅能绑定一个 MongoDB 用户,反之亦然。
此外,此处的认证数据库需指定为 $external,即所有基于 x.509 证书认证的用户都注册在 $external 数据库下。
首先,借助 openssl x509 工具,我们可以快速从客户端证书中读取其主题信息:
|openssl x509 -in client1.pem -inform PEM -subject -nameopt RFC2253 | grep subject
这应该产生以下输出:
|subject= CN=client1,OU=MyClients,O=MongoDB,L=Shenzhen,ST=GD,C=CN
接下来,我们使用 mongo shell 连接到副本集的主节点,并执行创建管理员用户的命令:
|mongo --norc localhost:27017
从 mongo shell 中,我们将发出以下命令:
|db.getSiblingDB("$external").runCommand( { createUser: "CN=client1,OU=MyClients,O=MongoDB,L=Shenzhen,ST=GD,C=CN", roles: [ { role: "readWrite", db: 'test' }, { role: "userAdminAnyDatabase", db: "admin" }, { role: "clusterAdmin", db:"admin"} ], writeConcern: { w: "majority" , wtimeout: 5000 } } );
上面的命令中我们使用了 $external 数据库,并将客户端证书的主题(Subject)作为 MongoDB 用户名进行添加,这符合 x.509 认证机制的要求。
管理员用户创建好之后,我们就可以重启副本集,使其启用认证和授权功能。这样,我们就能通过刚创建的管理员账户,以安全的方式连接和管理集群。如果还没有用户被注册,在开启认证后将无法正常访问或管理副本集,所以我们一定要先创建好管理员用户。 接下来,我们先停掉当前(还没开启认证)的副本集进程。
|kill $(ps -ef | grep mongod | grep set509 | awk '{print $2}')
接下来,我们要重启副本集,并让它正式启用认证。在生产环境下,通常会把每台服务器各自要用到的证书和密钥文件拷贝到对应主机上。不过为了演示方便,我们这里还是在本机上操作。为了让副本集安全运行,我们需要在每次启动 mongod 时加上一些安全相关的参数:
|--tlsMode --clusterAuthMode --tlsCAFile - 根 CA 文件(root-ca.pem) --tlsCertificateKeyFile - mongod 的证书文件 --tlsAllowInvalidHostnames - 仅用于测试;允许无效主机名
我们这里给 mongod 的 tlsCAFile 选项传入 root-ca.pem 文件,这样就建立了证书信任链。还记得吗?root-ca.pem 其实包含了根 CA 和签名 CA 的证书,所以只要指定这个文件,mongod 就会信任它以及被它签发的其他证书。 好了,现在我们来实际操作一下吧。
|mport=$mongodb_port for host in "${mongodb_server_hosts[@]}"; do echo "启动服务器 $host" mongod --replSet set509 --port $mport --dbpath ./db/$host \ --tlsMode requireTLS --clusterAuthMode x509 --tlsCAFile root-ca.pem \ --tlsAllowInvalidHostnames --fork --logpath ./db/${host}.log \
到这里,我们的三节点副本集已经支持 x.509 证书认证和传输加密啦。接下来,我们可以用 mongo shell 来连上去。这里我们会用 client1 的证书,因为刚才已经为它创建了管理员账号。
|mongo --norc --tls --tlsCertificateKeyFile client1.pem --tlsCAFile root-ca.pem \ --tlsAllowInvalidHostnames --authenticationDatabase "\$external" \ --authenticationMechanism MONGODB-X509
连接上之后,你可以试着往集合里插入一些数据,也不妨用其他用户(比如 client2.pem)尝试连接。这时你会发现,像这样未授权的用户将无法正常访问,连接时会遇到类似下面的报错。
|mongo --norc --tls --tlsCertificateKeyFile client2.pem --tlsCAFile root-ca.pem \ --tlsAllowInvalidHostnames --authenticationDatabase "\$external" \ --authenticationMechanism MONGODB-X509
|MongoDB shell version v4.2.0 2019-09-11T23:18:31.696+0100 W NETWORK [js] The server certificate does not match the host name. Hostname: 127.0.0.1 does not match 2019-09-11T23:18:31.702+0100 E QUERY [js] Error: Could not find user "CN=client2,OU=MyClients,O=MongoDB,L=Shenzhen,ST=GD,C=CN" for db "$external" : connect@src/mongo/shell/mongo.js:341:17 @(connect):3:6 2019-09-11T23:18:31.707+0100 F - [main] exception: connect failed 2019-09-11T23:18:31.707+0100 E - [main] exiting with code 1
通过本节课,我们已经用 x.509 证书实现了 MongoDB 副本集间和客户端之间的加密通信,这些方法其实也同样适合分片集群。在实际保护 MongoDB 集群时,有几个点需要特别注意: