1.5. 安全性

在本文档中,我们将介绍 CouchDB 中的基本安全机制:基本身份验证Cookie 身份验证。这是 CouchDB 处理用户并保护其凭据的方式。

1.5.1. 身份验证

CouchDB 具有一个管理员用户的概念(例如,管理员、超级用户或 root),该用户被允许对 CouchDB 安装执行任何操作。默认情况下,必须创建一个管理员用户才能使 CouchDB 成功启动。

CouchDB 还定义了一组只有管理员用户才能执行的请求。如果您已定义一个或多个特定管理员用户,CouchDB 将在某些请求时要求进行身份验证。

1.5.1.1. 创建新的管理员用户

如果您的安装过程未设置管理员用户,则必须手动将管理员用户添加到配置文件中,并首先重启 CouchDB。为了便于说明,我们将创建一个名为 admin 的默认用户,其密码为 password

警告

不要在没有思考的情况下直接输入以下内容!为您的管理员用户选择一个不容易猜到的好名字,并选择一个安全的密码。

在您的 etc/local.ini 文件末尾,在 [admins] 行之后,添加文本 admin = password,使其看起来像这样

[admins]
admin = password

(不用担心密码以明文形式出现;我们稍后会解决这个问题。)

现在,使用适合您的操作系统的适当方法重启 CouchDB。您现在应该能够使用您的新管理员帐户访问 CouchDB。

> curl http://admin:password@127.0.0.1:5984/_up
{"status":"ok","seeds":{}}

太棒了!

让我们通过 HTTP API 创建一个管理员用户。我们将她命名为 anna,她的密码是 secret。请注意以下代码中的双引号;它们是用来表示 配置 API 的字符串值的。

> HOST="http://admin:[email protected]:5984"
> NODENAME="_local"
> curl -X PUT $HOST/_node/$NODENAME/_config/admins/anna -d '"secret"'
""

根据 _config API 的行为,我们正在获取刚刚写入的配置项的先前值。由于我们的管理员用户不存在,因此我们得到一个空字符串。

请注意,_local 充当本地节点名称的别名,因此对于所有配置 URL,NODENAME 可以设置为 _local,以与本地节点的配置进行交互。

另请参见

节点管理

1.5.1.1.1. 哈希密码

看到明文密码很可怕,不是吗?不用担心,CouchDB 不会在任何地方显示明文密码。它会立即被哈希化。现在,请查看您的 local.ini 文件。您会发现 CouchDB 已重写了明文密码,使其被哈希化。

[admins]
admin = -pbkdf2-71c01cb429088ac1a1e95f3482202622dc1e53fe,226701bece4ae0fc9a373a5e02bf5d07,10
anna = -pbkdf2-2d86831c82b440b8887169bd2eebb356821d621b,5e11b9a9228414ab92541beeeacbf125,10

哈希值是那个又大又丑又长的字符串,它以 -pbkdf2- 开头。

为了在身份验证期间将明文密码与存储的哈希值进行比较,会运行哈希算法,并将生成的哈希值与存储的哈希值进行比较。对于不同密码的两个相同哈希值的概率微不足道(参见 Bruce Schneier)。如果存储的哈希值落入攻击者手中,根据目前的标准,从哈希值中找到明文密码非常不方便(即,需要大量资金和时间)。

当 CouchDB 启动时,它会读取一组包含配置设置的 .ini 文件。它将这些设置加载到一个内部数据存储中(不是数据库)。配置 API 允许您读取当前配置,以及更改配置和创建新条目。CouchDB 会将任何更改写回 .ini 文件。

当 CouchDB 未运行时,也可以手动编辑 .ini 文件。与我们之前展示的创建管理员用户的方式不同,您可以停止 CouchDB,打开您的 local.ini,在 admins 中添加 anna = secret,然后重启 CouchDB。在从 local.ini 中读取新行时,CouchDB 会运行哈希算法并将哈希值写回 local.ini,替换明文密码——就像它对我们最初的 admin 用户所做的那样。为了确保 CouchDB 仅对明文密码进行哈希化,而不是对现有哈希值进行二次哈希化,它会在哈希值前面加上 -pbkdf2-,以区分明文密码和 PBKDF2 哈希密码。这意味着您的明文密码不能以字符 -pbkdf2- 开头,但这本来就很不可能发生。

1.5.1.2. 基本身份验证

除非我们提供正确的管理员用户凭据,否则 CouchDB 不会允许我们创建新数据库。让我们验证一下。

> HOST="http://127.0.0.1:5984"
> curl -X PUT $HOST/somedatabase
{"error":"unauthorized","reason":"You are not a server admin."}

看起来很正常。现在,我们使用正确的凭据再次尝试。

> HOST="http://anna:[email protected]:5984"
> curl -X PUT $HOST/somedatabase
{"ok":true}

如果您曾经访问过受密码保护的网站或 FTP 服务器,那么 username:password@ URL 变体应该看起来很熟悉。

如果您注重安全,那么在 http:// 中缺少的 s 会让您感到不安。我们正在以明文形式将密码发送到 CouchDB。这是一件坏事,对吧?是的,但请考虑我们的场景:CouchDB 在我们作为唯一用户的开发机器上监听 127.0.0.1。谁可能嗅探我们的密码呢?

但是,如果您处于生产环境中,则需要重新考虑。您的 CouchDB 实例会通过公共网络进行通信吗?即使是与其他托管客户共享的 LAN 也是公共的。有多种方法可以确保您或您的应用程序与 CouchDB 之间的通信安全,这超出了本文档的范围。从版本 1.1.0 开始,CouchDB 自带 内置 SSL

1.5.2. 身份验证数据库

您可能已经注意到 CouchDB 管理员是在配置文件中定义的,并且想知道普通用户是否也存储在那里。不,他们没有。CouchDB 拥有一个特殊的 身份验证数据库,默认情况下名为 _users,它将所有注册用户存储为 JSON 文档。

此特殊数据库是一个 系统数据库。这意味着虽然它共享通用的 数据库 API,但会应用一些特殊的安全相关约束。以下是 身份验证数据库 与其他数据库的不同之处。

  • 只有管理员可以浏览所有文档的列表 (GET /_users/_all_docs)

  • 只有管理员可以监听 更改馈送 (GET /_users/_changes)

  • 只有管理员可以执行设计函数,例如 视图

  • 有一个特殊的 _auth 设计文档,它不能被修改

  • 除了 设计文档 之外的每个文档都代表注册的 CouchDB 用户,并且属于他们

  • 默认情况下,_users 数据库的 _security 设置禁止用户访问或修改文档

注意

可以更改设置,以便用户确实可以访问 _users 数据库,但即使那样,他们也只能访问 (GET /_users/org.couchdb.user:Jan) 或修改 (PUT /_users/org.couchdb.user:Jan) 他们拥有的文档。这在 CouchDB 4.0 中将不再可能。

这些严厉的规则是必要的,因为 CouchDB 关心其用户的个人信息,并且不会将其透露给任何人。通常,用户文档包含系统信息,例如 登录密码哈希角色,以及敏感的个人信息,例如真实姓名、电子邮件、电话、特殊内部标识等。这不是您想与世界分享的信息。

1.5.2.1. 用户文档

每个 CouchDB 用户都以文档格式存储。这些文档包含几个 CouchDB 用于身份验证的必需字段

  • _id (string): 文档 ID。包含用户的登录名,带有特殊前缀 为什么使用 org.couchdb.user: 前缀?

  • derived_key (string): 从盐/迭代派生的 PBKDF2 密钥。

  • name (string): 用户名,也称为登录名。不可变,例如,您不能重命名现有用户 - 您必须创建一个新的用户

  • roles (string 数组): 用户角色列表。CouchDB 不提供任何内置角色,因此您可以根据需要自由定义自己的角色。但是,您不能在那里设置系统角色,例如 _admin。此外,只有管理员可以为用户分配角色 - 默认情况下,所有用户都没有角色

  • password (string): 可以提供明文密码,但在实际存储文档之前,它将被哈希字段替换。

  • password_sha (string): 带有盐的哈希密码。用于 simple password_scheme

  • password_scheme (string): 密码哈希方案。可以是 simplepbkdf2

  • salt (string): 哈希盐。用于 simplepbkdf2 password_scheme 选项。

  • iterations (integer): 用于派生密钥的迭代次数,用于 pbkdf2 password_scheme 请参阅 配置 API:: 有关详细信息。

  • type (string): 文档类型。始终具有值 user

此外,您还可以指定与目标用户相关的任何自定义字段。

1.5.2.1.1. 为什么使用 org.couchdb.user: 前缀?

在用户登录名之前使用特殊前缀的原因是为了拥有用户所属的命名空间。此前缀旨在防止在尝试合并两个或多个 _user 数据库时发生复制冲突。

对于当前的 CouchDB 版本,所有用户都属于同一个 org.couchdb.user 命名空间,并且无法更改。这可能会在将来的版本中更改。

1.5.2.2. 创建新用户

创建新用户是一个非常简单的操作。您只需要使用用户的 PUT 请求将数据发送到 CouchDB。让我们创建一个登录名为 jan、密码为 apple 的用户

curl -X PUT http://localhost:5984/_users/org.couchdb.user:jan \
     -H "Accept: application/json" \
     -H "Content-Type: application/json" \
     -d '{"name": "jan", "password": "apple", "roles": [], "type": "user"}'

curl 命令将生成以下 HTTP 请求

PUT /_users/org.couchdb.user:jan HTTP/1.1
Accept: application/json
Content-Length: 62
Content-Type: application/json
Host: localhost:5984
User-Agent: curl/7.31.0

CouchDB 将以以下方式回复

HTTP/1.1 201 Created
Cache-Control: must-revalidate
Content-Length: 83
Content-Type: application/json
Date: Fri, 27 Sep 2013 07:33:28 GMT
ETag: "1-e0ebfb84005b920488fc7a8cc5470cc0"
Location: https://127.0.0.1:5984/_users/org.couchdb.user:jan
Server: CouchDB (Erlang OTP)

{"ok":true,"id":"org.couchdb.user:jan","rev":"1-e0ebfb84005b920488fc7a8cc5470cc0"}

文档已成功创建!用户 jan 现在应该存在于我们的数据库中。让我们检查一下是否属实

curl -X POST http://localhost:5984/_session -d 'name=jan&password=apple'

CouchDB 应该以以下方式回复

{"ok":true,"name":"jan","roles":[]}

这意味着用户名已识别,密码的哈希值与存储的哈希值匹配。如果我们指定了错误的登录名和/或密码,CouchDB 将以以下错误消息通知我们

{"error":"unauthorized","reason":"Name or password is incorrect."}

1.5.2.3. 更改密码

让我们从 CouchDB 和身份验证数据库的角度定义什么是更改密码。由于“用户”是“文档”,因此此操作只是使用包含明文密码的特殊字段 password 更新文档。害怕吗?不必害怕。身份验证数据库在文档更新时有一个特殊的内部钩子,它会查找此字段,并根据所选的 password_scheme安全哈希替换它。

总结上述过程 - 我们需要获取文档的内容,添加包含新密码的明文字段 password,然后将 JSON 结果存储到身份验证数据库中。

curl -X GET http://localhost:5984/_users/org.couchdb.user:jan
{
    "_id": "org.couchdb.user:jan",
    "_rev": "1-e0ebfb84005b920488fc7a8cc5470cc0",
    "derived_key": "e579375db0e0c6a6fc79cd9e36a36859f71575c3",
    "iterations": 10,
    "name": "jan",
    "password_scheme": "pbkdf2",
    "roles": [],
    "salt": "1112283cf988a34f124200a050d308a1",
    "type": "user"
}

这是我们的用户文档。我们可以从存储的文档中删除哈希值,以减少发布的数据量

curl -X PUT http://localhost:5984/_users/org.couchdb.user:jan \
     -H "Accept: application/json" \
     -H "Content-Type: application/json" \
     -H "If-Match: 1-e0ebfb84005b920488fc7a8cc5470cc0" \
     -d '{"name":"jan", "roles":[], "type":"user", "password":"orange"}'
{"ok":true,"id":"org.couchdb.user:jan","rev":"2-ed293d3a0ae09f0c624f10538ef33c6f"}

已更新!现在让我们检查一下密码是否真的已更改

curl -X POST http://localhost:5984/_session -d 'name=jan&password=apple'

CouchDB 应该以以下方式回复

{"error":"unauthorized","reason":"Name or password is incorrect."}

看起来密码 apple 是错误的,orange 呢?

curl -X POST http://localhost:5984/_session -d 'name=jan&password=orange'

CouchDB 应该以以下方式回复

{"ok":true,"name":"jan","roles":[]}

万岁!您可能想知道为什么这么复杂 - 我们需要检索用户的文档,向其中添加一个特殊字段,然后将其发布回去。

注意

API 请求没有密码确认:您应该在应用程序层实现它。

1.5.3. 授权

现在您已经拥有了一些可以登录的用户,您可能希望根据他们的身份和角色设置一些限制,以限制他们可以执行的操作。CouchDB 服务器上的每个数据库都可以包含自己的一组授权规则,这些规则指定哪些用户被允许读取和写入文档、创建设计文档以及更改某些数据库配置参数。授权规则由服务器管理员设置,可以随时修改。

数据库授权规则将用户分配到以下两个类别之一:

  • 成员,他们被允许读取所有文档,并创建和修改除设计文档以外的任何文档。

  • 管理员,他们可以读取和写入所有类型的文档,修改哪些用户是成员或管理员,并设置某些每个数据库的配置选项。

请注意,数据库管理员与服务器管理员不同——数据库管理员的操作仅限于特定数据库。

默认情况下,所有数据库都是以仅管理员的方式创建的。也就是说,只有数据库管理员可以读取或写入。默认行为可以使用 [couchdb] default_security 选项 进行配置。如果您将该选项设置为 everyone,则没有身份验证凭据或具有普通用户凭据的 HTTP 请求将被视为成员,而具有服务器管理员凭据的请求将被视为数据库管理员。

您也可以在数据库创建后修改权限,方法是修改数据库中的 安全 文档。

> curl -X PUT http://localhost:5984/mydatabase/_security \
     -u anna:secret \
     -H "Content-Type: application/json" \
     -d '{"admins": { "names": [], "roles": [] }, "members": { "names": ["jan"], "roles": [] } }'

创建或更新 _security 文档的 HTTP 请求必须包含服务器管理员的凭据。CouchDB 将响应:

{"ok":true}

该数据库现在已针对匿名读取和写入进行了保护。

> curl http://localhost:5984/mydatabase/
{"error":"unauthorized","reason":"You are not authorized to access this db."}

您已将用户“jan”声明为该数据库的成员,因此他能够读取和写入普通文档。

> curl -u jan:apple http://localhost:5984/mydatabase/
{"db_name":"mydatabase","doc_count":1,"doc_del_count":0,"update_seq":3,"purge_seq":0,
"compact_running":false,"sizes":{"active":272,"disk":12376,"external":350},
"instance_start_time":"0","disk_format_version":6,"committed_update_seq":3}

但是,如果 Jan 尝试创建设计文档,CouchDB 将返回 401 未授权错误,因为用户名“jan”不在管理员名称列表中,并且 /_users/org.couchdb.user:jan 文档不包含与任何已声明的管理员角色匹配的角色。如果您想将 Jan 提升为管理员,您可以更新安全文档,将 “jan” 添加到 admin 下的 names 数组中。但是,跟踪单个数据库管理员用户名很繁琐,因此您可能更愿意创建一个数据库管理员角色,并将该角色分配给 org.couchdb.user:jan 用户文档。

> curl -X PUT http://localhost:5984/mydatabase/_security \
     -u anna:secret \
     -H "Content-Type: application/json" \
     -d '{"admins": { "names": [], "roles": ["mydatabase_admin"] }, "members": { "names": [], "roles": [] } }'

有关指定数据库成员和管理员的更多详细信息,请参阅 _security 文档参考页面