介绍

MongoDB,也称为_Mongo_,是一个在许多现代Web应用程序中使用的开源文档数据库。它被归类为 NoSQL 数据库,因为它不依赖关系数据库模型。相反,它使用具有动态模式的类似 JSON 的文档。这意味着,与 关系数据库 不同,MongoDB 在将数据添加到数据库之前不需要预定义模式。

当您使用多个分布式 MongoDB 实例时,例如 副本集 或 [分片数据库架构](https://docs .mongodb.com/manual/sharding/index.html),确保它们之间的通信安全很重要。一种方法是通过 keyfile authentication。这涉及创建一个特殊文件,该文件基本上用作集群中每个成员的共享密码。

本教程概述了如何更新现有副本集以使用密钥文件身份验证。本指南中涉及的过程还将确保副本集不会经历任何停机时间,因此副本集中的数据将仍然可供任何需要访问它的客户端或应用程序使用。

先决条件

要完成本教程,您需要:

  • 三台服务器,每台运行 Ubuntu 20.04。所有这三个服务器都应该有一个管理非 root 用户和一个配置了 UFW 的防火墙。要进行设置,请按照我们的 Ubuntu 20.04 初始服务器设置指南。

  • MongoDB 安装在您的每台 Ubuntu 服务器上。按照我们关于如何在 Ubuntu 20.04 上安装 MongoDB 的教程,确保完成每个踏上您的每台服务器。

  • 所有三个 MongoDB 安装都配置为副本集。按照本教程[如何在 Ubuntu 20.04 上配置 MongoDB 副本集](https://www.digitalocean.com/community/tutorials/how-to-configure-a-mongodb-replica-set-on-ubuntu-20 -04) 进行设置。

  • 为每个服务器生成的 SSH 密钥。此外,您应该确保每台服务器都将其他两台服务器的公钥添加到其 authorized_keys 文件中。这是为了确保每台机器可以通过 SSH 相互通信,这将更容易在第 2 步中将密钥文件分发给每台机器。要设置它们,请按照我们的指南如何在 Ubuntu 上设置 SSH 密钥20.04。

请注意,为清楚起见,本指南将遵循先决条件副本集教程中建立的约定,并将三个服务器称为 mongo0mongo1mongo2。它还将假定您已完成 [该指南的第 1 步](https://www.digitalocean.com/community/tutorials/how-to-configure-a-mongodb-replica-set-on-ubuntu-20 -04#step-1-%E2%80%94-configuring-dns-resolution) 并配置每个服务器的 hosts 文件,以便以下主机名将解析为给定服务器的 IP 地址:

主机名

决议为

mongo0.replset.member

mongo0

mongo1.replset.member

mongo1

mongo2.replset.member

漫画

在本指南中的一些实例中,您必须仅在其中一个服务器上运行命令或更新文件。在这种情况下,本指南将默认在示例中使用 mongo0,并通过在蓝色背景中显示命令或文件更改来表示这一点,如下所示:

必须在多个服务器上运行的任何命令或必须进行的文件更改都将具有标准的灰色背景,如下所示:

关于密钥文件身份验证

在 MongoDB 中,keyfile 身份验证依赖于数据库系统默认的身份验证机制 Salted Challenge Response Authentication Mechanism (SCRAM)。 SCRAM 涉及 MongoDB 根据用户名、密码和身份验证数据库的组合读取和验证用户提供的凭据,所有这些都为给定的 MongoDB 实例所知。这与用于对连接数据库时提供密码的用户进行身份验证的机制相同。

在密钥文件身份验证中,密钥文件充当集群中每个成员的共享密码。密钥文件必须包含 6 到 1024 个字符。密钥文件只能包含 base64 集中的字符,请注意 MongoDB 在读取密钥时会去除空白字符。从 Mongo 4.2 版本开始,密钥文件使用 YAML 格式,允许您在单个密钥文件中共享多个密钥。

警告: MongoDB 的社区版带有两种身份验证方法,可以帮助确保您的数据库安全,keyfile authenticationx.509 authentication。对于使用复制的生产部署,MongoDB 文档建议使用 x.509 身份验证,并将密钥文件描述为“最适合测试或开发环境”的“最低限度的安全形式”。

获取和配置 x.509 证书的过程伴随着一些必须根据具体情况做出的警告和决定,这意味着此过程超出了 DigitalOcean 教程的范围。如果您计划在生产环境中使用副本集,我们强烈建议您查看 [有关 x.509 身份验证的官方 MongoDB 文档](https://docs.mongodb.com/manual/tutorial/configure -x509-成员身份验证/)。

如果您计划使用您的副本集进行测试或开发,您可以继续按照本教程为您的集群添加一层安全性。

步骤 1 — 创建用户管理员

当您在 MongoDB 中启用身份验证时,它还将为副本集启用_基于角色的访问控制_。根据 MongoDB 文档:

MongoDB 使用基于角色的访问控制 (RBAC) 来管理对 MongoDB 系统的访问。用户被授予一个或多个角色,这些角色决定了用户对数据库资源和操作的访问权限。

在 MongoDB 实例上启用访问控制后,这意味着您将无法访问系统上的任何资源,除非您已作为有效的 MongoDB 用户进行身份验证。即使这样,您也必须以具有访问给定资源的适当权限的用户身份进行身份验证。

如果您在启用密钥文件身份验证(因此,访问控制)之前没有为您的 MongoDB 系统创建用户,您将不会被锁定在您的副本集之外。您可以创建一个 MongoDB 用户,您可以使用该用户对集合进行身份验证,并在必要时通过 Mongo 的 localhost exception 创建其他用户-例外)。这是 MongoDB 为启用访问控制但缺少用户的配置创建的一个特殊例外。此异常仅允许您连接到 localhost 上的数据库,然后在 admin 数据库中创建用户。

但是,在启用身份验证后依赖 localhost 异常创建 MongoDB 用户意味着您的副本集将经历一段停机时间,因为在您创建用户之前,副本将无法验证其连接。此步骤概述了如何在启用身份验证之前创建用户,以确保您的副本集保持可用。该用户将有权在数据库上创建其他用户,从而使您可以自由地创建具有他们将来需要的任何权限的其他用户。在 MongoDB 中,具有此类权限的用户称为_用户管理员_。

首先,连接到副本集的主要成员。如果您不确定哪个成员是主要成员,您可以运行 rs.status() 方法来识别它。

在副本集中托管 MongoDB 实例的任何 Ubuntu 服务器的 bash 提示符下运行以下 mongo 命令。此命令的 --eval 选项指示 mongo 操作不要打开当您单独运行 mongo 时出现的 shell 界面环境,而是运行用单引号括起来的命令或方法,它跟在 - -eval 参数:

mongo --eval 'rs.status()'

rs.status() 返回很多信息,但输出的相关部分是 "members" : 数组。在 MongoDB 的上下文中,数组是保存在一对方括号([])之间的文档集合。

"members": 数组中,您会找到许多文档,每个文档都包含有关副本集中一个成员的信息。在每个成员文档中,找到“stateStr”字段。 "stateStr" 值为 "PRIMARY" 的成员是副本集的主要成员。以下示例显示了 mongo0 是主要的情况:

输出。 . .
“成员”:[
{
“_id”:0,
“名称”:“mongo0.replset.member:27017”,
“健康”:1,
“状态”:1,
“stateStr”:“主要”,
. . .
},
. . .

一旦您知道哪些副本集成员是主要成员,就可以通过 SSH 连接到托管该实例的服务器。出于演示目的,本指南将继续使用以 mongo0 为主的示例:

ssh sammy@mongo0_ip_address

登录服务器后,打开 mongo shell 环境连接到 MongoDB:

蒙哥

在 MongoDB 中创建用户时,您必须在将用作其_身份验证数据库_的特定数据库中创建它们。用户名及其身份验证数据库的组合用作该用户的唯一标识符。

某些管理操作仅适用于身份验证数据库为“admin”数据库(每个 MongoDB 安装中包含的特殊特权数据库)的用户,包括创建新用户的能力。因为这一步的目标是创建一个可以在副本集中创建其他用户的用户管理员,所以连接到 admin 数据库,以便您可以授予该用户适当的权限:

使用管理员

输出切换到 db admin

MongoDB 安装了 许多基于 JavaScript 的 shell 方法,可用于管理数据库。其中之一,db.createUser 方法,用于在运行该方法的数据库中创建新用户。

启动 db.createUser 方法:

db.createUser(

注意:在您输入右括号之前,Mongo 不会将 db.createUser 方法注册为完整的。在您这样做之前,提示将从大于号 (>) 变为省略号 (...)。

此方法要求您指定用户的用户名和密码,以及您希望用户拥有的任何角色。回想一下,MongoDB 将其数据存储在类似 JSON 的文档中;当你创建一个新用户时,你所做的就是创建一个文档来保存适当的用户数据作为单独的字段。

与 JSON 中的对象一样,MongoDB 中的文档以大括号({})开始和结束。输入一个左大括号以开始用户文档:

{

接下来,输入“用户:”字段,将所需的用户名作为双引号中的值,后跟逗号。以下示例指定用户名 UserAdminSammy,但您可以输入您喜欢的任何用户名:

用户:“UserAdminSammy”,

接下来,输入一个以 passwordPrompt() 方法作为其值的 pwd 字段。当您执行 db.createUser 方法时,passwordPrompt() 方法会提示您输入密码。这比替代方法更安全,即像输入用户名一样以明文形式输入密码。

注意:passwordPrompt() 方法仅与 MongoDB 4.2 及更高版本兼容。如果您使用的是旧版本的 Mongo,则必须以明文形式写出密码,类似于您写出用户名的方式:

密码:“密码”,

请务必在此字段后面加上逗号:

密码:密码提示(),

然后输入一个“角色”字段,后跟一个数组,详细说明您希望管理用户拥有的角色。在 MongoDB 中,roles 定义用户可以对他们有权访问的资源执行哪些操作。您可以自己定义自定义角色,但 Mongo 还附带了许多授予常用权限的内置角色。

因为您正在创建用户管理员,所以您至少应该在 admin 数据库上授予他们内置的 userAdminAnyDatabase 角色。这将允许用户管理员创建和修改新用户和角色。因为管理用户在 admin 数据库中有这个角色,这也将授予它对整个集群的超级用户访问权限:

角色:[{角色:“userAdminAnyDatabase”,db:“admin”}]

之后,输入一个右大括号来表示文档的结束:

}

然后输入右括号关闭并执行db.createUser方法:

)

总之,你的 db.createUser 方法应该是这样的:

> db.createUser(
... {
...用户:“UserAdminSammy”,
...密码:密码提示(),
...角色:[{角色:“userAdminAnyDatabase”,db:“admin”}]
... }
... )

如果每一行的语法正确,该方法将正确执行,并提示您输入密码:

输出输入密码:

输入您选择的强密码。然后,您将收到用户已添加的确认信息:

输出成功添加用户:{
“用户”:“用户管理员萨米”,
“角色”:[
{
“角色”:“userAdminAnyDatabase”,
“分贝”:“管理员”
},
“readWriteAnyDatabase”
]
}

这样,您就添加了一个 MongoDB 用户配置文件,您可以使用它来管理系统上的其他用户和角色。您可以通过创建另一个用户来测试这一点,如本步骤的其余部分所述。

首先以您刚刚创建的用户管理员身份进行身份验证:

db.auth("UserAdminSammy", passwordPrompt())

如果身份验证成功,db.auth() 将返回 1:

输出1

注意:将来,如果您想在连接到集群时以用户管理员身份进行身份验证,您可以直接在服务器提示符下使用如下命令执行此操作:

mongodb -u "UserAdmin Sammy" -p --authenticationDatabase "admin"

在此命令中,-u 选项告诉 shell 以下参数是您要进行身份验证的用户名。 -p 标志告诉它提示您输入密码,并且 --authenticationDatabase 选项位于用户身份验证数据库的名称之前。如果您输入了错误的密码或用户名和身份验证数据库不匹配,您将无法进行身份验证,您将不得不再次尝试连接。

另外,请注意,为了让您以用户管理员身份在副本集中创建新用户,您必须连接到该集的主要成员。

添加其他用户的步骤与添加用户管理员的步骤相同。以下示例创建了一个具有“clusterAdmin”角色的新用户,这意味着他们将能够执行许多与复制和分片相关的操作。在 MongoDB 的上下文中,具有这些权限的用户称为_集群管理员_。

拥有一个专门的用户来执行这样的特定功能是一种很好的安全实践,因为它限制了您在系统上拥有的特权用户的数量。在本教程后面启用密钥文件身份验证后,任何想要执行 clusterAdmin 角色允许的任何操作的客户端 - 例如任何 rs. 方法,如 rs.status()rs .conf() — 必须首先以集群管理员身份进行身份验证。

也就是说,您可以为该用户提供您想要的任何角色,并同样为他们提供不同的名称和身份验证数据库。但是,如果您希望新用户充当集群管理员,那么您必须授予他们在 admin 数据库中的 clusterAdmin 角色。

除了创建用户作为集群管理员之外,以下方法将用户命名为 ClusterAdminSammy 并使用 passwordPrompt() 方法提示您输入密码:

db.createUser(
{
用户:“ClusterAdminSammy”,
密码:密码提示(),
角色:[{角色:“clusterAdmin”,数据库:“admin”}]
}
)

同样,如果您使用的是 4.2 之前的 MongoDB 版本,那么您将不得不以明文形式写出您的密码,而不是使用 passwordPrompt() 方法。

如果每一行的语法正确,该方法将正确执行,并提示您输入密码:

输出输入密码:

输入您选择的强密码。然后,您将收到用户已添加的确认信息:

输出成功添加用户:{
“用户”:“ClusterAdminSammy”,
“角色”:[
{
“角色”:“集群管理员”,
“分贝”:“管理员”
}
]
}

此输出确认您的用户管理员能够创建新用户并授予他们角色。您现在可以关闭 MongoDB shell:

出口

或者,您可以通过按 CTRL + C 关闭 shell。

此时,如果您有任何客户端或应用程序连接到您的 MongoDB 集群,那么现在是创建一个或多个具有适当角色的专用用户的好时机,他们可以使用这些用户对数据库进行身份验证。否则,请继续阅读以了解如何生成密钥文件,将其分发给副本集的成员,然后将每个配置为要求副本集成员使用密钥文件进行身份验证。

第 2 步 - 创建和分发身份验证密钥文件

在创建密钥文件之前,在您将存储密钥文件的每个服务器上创建一个目录以使事情井井有条会很有帮助。运行以下命令,它会在 Ubuntu 管理用户的主目录中创建一个名为 mongo-security 的目录,在您的三台服务器上:

mkdir ~/mongo-security

然后在您的一个服务器上生成一个密钥文件。您可以在任何一台服务器上执行此操作,但出于说明目的,本指南将在 mongo0 上生成密钥文件。

导航到您刚刚创建的 mongo-security 目录:

cd ~/mongo-security/

在该目录中,使用以下 openssl 命令创建一个密钥文件:

openssl rand -base64 768 > keyfile.txt

记下这个命令的参数:

  • rand:指示 OpenSSL 生成伪随机字节数据

  • -base64:指定该命令应使用 base64 编码将伪随机数据表示为可打印文本。这很重要,因为如前所述,MongoDB 密钥文件只能包含 base64 集中的字符

  • 768:命令应该生成的字节数。在 base64 编码中,三个二进制字节的数据表示为四个字符。因为 MongoDB 密钥文件最多可以有 1024 个字符,所以 768 是您可以为有效密钥文件生成的最大字节数

此命令的 768 参数后面是一个大于号 (>)。这会将命令的输出重定向到一个名为“keyfile.txt”的新文件中,该文件将用作您的密钥文件。如果您愿意,可以随意将密钥文件命名为 keyfile.txt 以外的名称,但请确保在以后的命令中出现时更改文件名。

接下来,修改密钥文件的权限,以便只有所有者具有读取权限:

chmod 400 密钥文件.txt

在此之后,将密钥文件分发到在您的副本集中托管 MongoDB 实例的另外两台服务器。假设您遵循 如何设置 SSH 密钥 的先决条件指南,您可以使用 scp 命令执行此操作:

scp keyfile.txt sammy@mongo1.replset.member:/home/sammy/mongo-security
scp keyfile.txt sammy@mongo2.replset.member:/home/sammy/mongo-security

请注意,这些命令中的每一个都将密钥文件直接复制到您之前在 mongo1mongo2 上创建的 ~/mongo-security/ 目录中。确保将 sammy 更改为您在每台服务器上创建的管理 Ubuntu 用户配置文件的名称。

接下来,将文件的所有者更改为 mongodb 用户配置文件。这是安装 MongoDB 时创建的特殊用户,用于运行 mongod 服务。此用户必须有权访问密钥文件,以便 MongoDB 使用它进行身份验证。

每个服务器 上运行以下命令,将密钥文件的所有者更改为 mongodb 用户帐户:

sudo chown mongodb:mongodb ~/mongo-security/keyfile.txt

在每台服务器上更改密钥文件的所有者后,您就可以重新配置每个 MongoDB 实例以强制执行密钥文件身份验证。

步骤 3 — 启用密钥文件身份验证

现在您已经生成了一个密钥文件并将其分发到副本集中的每个服务器,您可以更新每个服务器上的 MongoDB 配置文件以强制执行密钥文件身份验证。

为了避免在将副本集的成员配置为需要身份验证时出现任何停机,此步骤涉及首先重新配置集的辅助成员。然后,您将指示您的主要成员下台并成为次要成员。这将导致次要成员举行选举以选择新的主要成员,从而使您的集群可供任何需要访问它的客户端或应用程序使用。然后,您将重新配置以前的主节点以启用身份验证。

在托管副本集次要成员的每台服务器上,使用您喜欢的文本编辑器打开 MongoDB 的配置文件:

Sudonano / 等 / Mongo d. conf

在文件中,找到“安全”部分。默认情况下它看起来像这样:

/etc/mongod.conf

. . .
#安全:
. . .

通过删除井号 (#) 取消注释此行。然后,在下一行,添加一个 keyFile: 指令,后跟您在上一步中创建的密钥文件的完整路径:

/etc/mongod.conf

. . .
安全:
密钥文件:/home/sammy/mongo-security/keyfile.txt
. . .

请注意,此新行的开头有两个空格。这些是正确读取配置文件所必需的。当您在自己的配置文件中输入此行时,请确保您提供的路径反映了每个服务器上密钥文件的实际路径。

keyFile 指令下方,添加一个值为 truetransitionToAuth 指令。当设置为 true 时,此配置选项允许 MongoDB 实例接受经过身份验证和未经过身份验证的连接。这在重新配置副本集以强制执行身份验证时很有用,因为它将确保您的数据在您重新启动集合的每个成员时保持可用:

/etc/mongod.conf

. . .
安全:
密钥文件:/home/sammy/mongo-security/keyfile.txt
过渡到身份验证:真
. . .

同样,请确保在 transitionToAuth 指令之前包含两个空格。

进行这些更改后,保存并关闭文件。如果你使用 nano 来编辑它,你可以通过按 CTRL + XY,然后按 ENTER 来完成。

然后在两个辅助实例的服务器上重新启动mongod服务以立即使这些更改生效:

sudo systemctl 重启 mongod

这样,您就为副本集的次要成员配置了密钥文件身份验证。此时,经过身份验证和未经过身份验证的用户都可以不受限制地访问这些成员。

接下来,您将对主要成员重复此过程。但是,在这样做之前,您必须让该成员下台,使其不再是主要成员。为此,请在托管主要成员的服务器上打开 MongoDB shell。出于说明目的,本指南将再次假设这是 mongo0:

蒙哥

在提示符下,运行 rs.stepDown() 方法。这将指示主要成员成为次要成员,并将导致当前的次要成员进行选举以确定哪个将充当新的主要成员:

rs.stepDown()

如果该方法在输出中返回 "ok" : 1,则表示主成员成功降级为辅助成员:

输出{
“好”:1,
“$clusterTime”:{
"clusterTime" : 时间戳(1614795467, 1),
“签名” : {
“哈希”:bindata(0,“aaaaaaaaaaaaaaaaaaaaaaaaaaaa u003d”),
"keyId" : NumberLong(0)
}
},
“操作时间”:时间戳(1614795467, 1)
}

降级后,您可以关闭 Mongo shell:

出口

接下来,打开此服务器上的 MongoDB 配置文件:

Sudonano / 等 / Mongo d. conf

找到安全部分并通过删除井号取消注释“安全”标题。然后添加您添加到其他 MongoDB 实例的相同 keyFiletransitionToAuth 指令。进行这些更改后,“安全”部分将如下所示:

/etc/mongod.conf

. . .
安全:
密钥文件:/home/sammy/mongo-security/keyfile.txt
过渡到身份验证:真
. . .

同样,确保 keyFile 指令之后的文件路径反映了密钥文件在此服务器上的实际位置。

完成后,保存并关闭文件。然后重启mongod进程:

sudo systemctl 重启 mongod

之后,您的所有 MongoDB 实例都能够接受经过身份验证和未经身份验证的连接。在本指南的最后一步中,您将配置实例以要求用户在执行特权操作之前进行身份验证。

第 4 步 — 在没有 transitionToAuth 的情况下重新启动每个成员以强制进行身份验证

此时,您的每个 MongoDB 实例都配置了 transitionToAuth 设置为 true。这意味着即使您已启用每台服务器使用您创建的密钥文件在内部对连接进行身份验证,它们仍然能够接受未经身份验证的连接。

要更改此设置并要求每个成员强制执行身份验证,请在每个服务器上重新打开 mongod.conf 文件**:

Sudonano / 等 / Mongo d. conf

找到 security 部分并禁用 transitionToAuth 指令。您可以通过在该行前面加上一个井号来注释该行来做到这一点:

/etc/mongod.conf

. . .
安全:
密钥文件:/home/sammy/mongo-security/keyfile.txt
#transitionToAuth:真
. . .

在每个实例的配置文件中禁用 transitionToAuth 指令后,保存并关闭每个文件。

然后,在每个服务器上重新启动 mongod 服务**:

sudo systemctl 重启 mongod

之后,副本集中的每个 MongoDB 实例都需要您进行身份验证才能执行特权操作。

要对此进行测试,请尝试运行一个 MongoDB 方法,该方法在由具有适当权限的经过身份验证的用户调用时起作用。尝试从任何 Ubuntu 服务器的提示符中运行以下命令:

mongo --eval 'rs.status()'

即使您在步骤 1 中成功运行了此方法,rs.status() 方法现在也只能由已被授予 clusterAdminclusterManager 角色的用户运行,因为您已启用密钥文件身份验证。无论您是在托管主要成员还是次要成员之一的服务器上运行此命令,它都不会工作,因为您尚未进行身份验证:

输出。 . .
MongoDB服务器版本:4.4.4
{
"operationTime" : 时间戳(1616184183, 1),
“好”:0,
"errmsg" : "命令 replSetGetStatus 需要身份验证",
“代码”:13,
"codeName" : "未经授权",
“$clusterTime”:{
"clusterTime" : 时间戳(1616184183, 1),
“签名” : {
“哈希”:BinData(0,"huJUmB/lrrxpx9YfnONM4mayJwou003d"),
"keyId" : NumberLong("6941116945081040899")
}
}
}

回想一下,在启用访问控制之后,所有集群管理方法(包括 rs.status() 之类的方法)只有在被授予适当集群管理角色的经过身份验证的用户调用时才能工作。如果您已创建集群管理员(如步骤 1 中所述)并以该用户身份进行身份验证,则此方法将按预期工作:

mongo -u "ClusterAdminSammy" -p --authenticationDatabase "admin" --eval 'rs.status()'

在提示时输入用户密码后,您将看到 rs.status() 方法的输出:

输出。 . .
MongoDB服务器版本:4.4.4
{
“设置”:“shard2”,
“日期”:ISODate(“2021-03-19T20:21:45.528Z”),
“我的状态”:2,
“术语”:NumberLong(4),
"syncSourceHost" : "mongo1.replset.member:27017",
“syncSourceId”:2,
“heartbeatIntervalMillis”:NumberLong(2000),
“多数投票数”:2,
. . .

这确认副本集正在执行身份验证,并且您能够成功进行身份验证。

结论

通过完成本教程,您使用 OpenSSL 创建了一个密钥文件,然后配置了一个 MongoDB 副本集以要求其成员将其用于内部身份验证。您还创建了一个用户管理员,它将允许您在将来管理用户和角色。在所有这一切中,您的副本集不会经历任何停机时间,您的数据将仍然可供您的客户端和应用程序使用。

如果您想了解有关 MongoDB 的更多信息,我们鼓励您查看我们的 整个 MongoDB 内容库。

Logo

MongoDB社区为您提供最前沿的新闻资讯和知识内容

更多推荐