1.5. 安全性¶
在本文档中,我们将了解CouchDB中的基本安全机制: Basic Authentication 和 Cookie Authentication . 这就是CouchDB如何处理用户并保护他们的凭据。
1.5.1. 认证¶
CouchDB有一个想法 管理用户 允许对CouchDB安装执行任何操作的管理员、超级用户或root用户。默认情况下,一个管理员用户 must 为CouchDB成功启动创建。
CouchDB还定义了一组仅允许管理员用户执行的请求。如果您已经定义了一个或多个特定的管理用户,CouchDB将为某些请求请求请求标识:
- 创建数据库 (:put:`PUT /database </{{db}}>` )
- 删除数据库 (:put:`DELETE /database </{{db}}>` )
- 设置数据库安全性 (:put:`PUT /database/_security </{{db}}/_security>` )
- 创建设计文档 (:put:`PUT /database/_design/app </{{db}}/_design/{{ddoc}}>` )
- 更新设计文档 (:put:`PUT /database/_design/app?rev=1-4E2 </{{db}}/_design/{{ddoc}}>` )
- 删除设计文档 (:delete:`DELETE /database/_design/app?rev=2-6A7 </{{db}}/_design/{{ddoc}}>` )
- 触发压实 (:post:`POST /database/_compact </{{db}}/_compact>` )
- 正在读取任务状态列表 (
GET /_active_tasks
) - 在给定节点上重新启动服务器 (:post:`POST /_node/{{node-name}}/_restart </_restart>` )
- 读取活动配置 (:get:`GET /_node/{{node-name}}/_config </_config>` )
- 更新活动配置 (:put:`PUT /_node/{{node-name}}/_config/section/key </_config/{{section}}/{{key}}>` )
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":{}}
太好了!
让我们通过httpapi创建一个管理用户。我们会打电话给她 anna
,她的密码是 secret
. 注意下面代码中的双引号;它们是用来表示 configuration API ::
> HOST="http://admin:password@127.0.0.1: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
具有配置设置的文件。它将这些设置加载到内部数据存储(而不是数据库)。ConfigAPI允许您读取当前配置、更改配置并创建新条目。CouchDB将任何更改写回 .ini
文件夹。
这个 .ini
当CouchDB不运行时,也可以手动编辑文件。您可以停止CouchDB,打开 local.ini
,补充说 anna = secret
到 admins
,并重新启动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:secret@127.0.0.1:5984"
> curl -X PUT $HOST/somedatabase
{"ok":true}
如果您曾经访问过受密码保护的网站或FTP服务器,则 username:password@
URL变体应该看起来很熟悉。
如果你有安全意识 s
在里面 http://
会让你紧张。我们把密码以明文形式发送到CouchDB。这是件坏事,对吧?是的,但是考虑我们的场景:CouchDB监听 127.0.0.1
在我们唯一用户的开发盒上。谁能嗅出我们的密码?
但是,如果您处于生产环境中,则需要重新考虑。您的CouchDB实例会通过公共网络进行通信吗?甚至与其他搭配客户共享的局域网也是公共的。有多种方法可以保护您或您的应用程序与CouchDB之间的通信,这些方法超出了本文的范围。CouchDB自版本起 1.1.0 附带 SSL built in .
1.5.1.3. Cookie身份验证¶
使用纯文本密码的基本身份验证很好而且很方便,但是如果不采取额外措施,则不太安全。这也是一个非常差的用户体验。如果使用基本身份验证来识别管理员,则应用程序的用户需要处理一个难看的、不稳定的浏览器模式对话框,该对话框显示工作中的非专业人员比其他任何东西都重要。
为了解决这些问题,CouchDB支持cookie身份验证。使用cookie身份验证,应用程序不必包括用户浏览器附带的难看的登录对话框。您可以使用常规的HTML表单向CouchDB提交登录信息。收到后,CouchDB将生成一个一次性令牌,客户机可以在下一次对CouchDB的请求中使用该令牌。当CouchDB在随后的请求中看到令牌时,它将基于令牌对用户进行身份验证,而不需要再次看到密码。默认情况下,令牌的有效期为10分钟。
要获得第一个令牌并因此首次对用户进行身份验证,必须将用户名和密码发送到 _session 应用程序编程接口。这个API足够智能,可以解码HTML表单提交,因此您不必在应用程序中使用任何智能。
如果不使用HTML表单登录,则需要发送一个HTTP请求,该请求看起来好像是HTML表单生成的。幸运的是,这非常简单:
> HOST="http://127.0.0.1:5984"
> curl -vX POST $HOST/_session \
-H 'Content-Type:application/x-www-form-urlencoded' \
-d 'name=anna&password=secret'
CouchDB回复,我们会给你更多的细节:
< HTTP/1.1 200 OK
< Set-Cookie: AuthSession=YW5uYTo0QUIzOTdFQjrC4ipN-D-53hw1sJepVzcVxnriEw;
< Version=1; Path=/; HttpOnly
> ...
<
{"ok":true}
A 200 OK 响应代码告诉我们一切都很好 Set-Cookie header包含我们可以用于下一个请求的令牌,标准JSON响应再次告诉我们请求成功。
现在,我们可以使用此令牌作为同一用户发出另一个请求,而无需再次发送用户名和密码:
> curl -vX PUT $HOST/mydatabase \
--cookie AuthSession=YW5uYTo0QUIzOTdFQjrC4ipN-D-53hw1sJepVzcVxnriEw \
-H "X-CouchDB-WWW-Authenticate: Cookie" \
-H "Content-Type:application/x-www-form-urlencoded"
{"ok":true}
默认情况下,您可以继续使用此令牌10分钟。10分钟后,您需要再次验证您的用户。可以使用中的超时(以秒为单位)设置来配置令牌生存期 chttpd_auth 配置节。
1.5.2. 身份验证数据库¶
您可能已经注意到CouchDB管理员是在配置文件中定义的,并且想知道普通用户是否也存储在那里。不,他们不是。CouchDB有一个特别的 authentication database, named ``_ 默认情况下,它将所有注册用户存储为JSON文档。
这个特殊的数据库是一个 system database . 这意味着,虽然它共享 database API ,应用了一些特殊的安全相关约束。下面是如何 authentication database 与其他数据库不同。
- 只有管理员可以浏览所有文档的列表 (:get:`GET /_users/_all_docs </{{db}}/_all_docs>` )
- Only administrators may listen to changes feed (
GET /_users/_changes
) - 只有管理员可以执行以下设计功能 views .
- 有专门的设计文件
_auth
无法修改 - 除了 design documents 表示注册的CouchDB用户并属于他们
- 默认情况下,
_security
的设置_users
数据库不允许用户访问或修改文档
注解
可以更改设置,以便用户可以访问 _users
数据库,但即使这样,他们也只能访问 (:get:`GET /_users/org.couchdb.user:Jan </{{db}}/{{docid}}>` )或修改 (:put:`PUT /_users/org.couchdb.user:Jan </{{db}}/{{docid}}>` )他们拥有的文件。这在CouchDB 4.0中是不可能的。
这些严格的规则是必要的,因为CouchDB关心用户的个人信息,不会向任何人透露。通常,用户文档包含系统信息,如 login , password hash 和 roles ,除了敏感的个人信息,如真实姓名、电子邮件、电话、特殊的内部身份证等等。这不是你想和全世界分享的信息。
1.5.2.1. 用户文档¶
每个CouchDB用户都以文档格式存储。这些文件包含几个 强制性的 CouchDB验证所需的字段:
- _id ( 一串 ):文档ID。包含带有特殊前缀的用户登录名 为什么 org.couchdb.user: 前缀?
- derived_key ( 字符串 ): PBKDF2 从SALT/迭代派生的密钥。
- name ( 一串 ):用户名aka login。 不变的 e、 你不能重命名一个现有的用户-你必须创建一个新的
- 角色 ( 数组 属于 一串 ):用户角色列表。CouchDB不提供任何内置角色,因此您可以根据需要自由定义自己的角色。但是,您不能将系统角色设置为
_admin
在那里。此外,只有管理员可以为用户分配角色-默认情况下,所有用户都没有角色 - 密码 ( 字符串 ):可以提供明文密码,但在实际存储文档之前将替换为散列字段。
- password_sha ( 一串 ):用salt散列密码。用于
simple
password_scheme - password_scheme ( 一串 ):密码哈希方案。可能是
simple
或pbkdf2
- salt ( 字符串 ):哈希盐。两者都使用
simple
和pbkdf2
password_scheme
选项。 - 迭代次数 ( 整数 ):派生密钥的迭代次数,用于
pbkdf2
password_scheme
- type ( 一串 ):文档类型。一直都有价值
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: http://localhost: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服务器上的每个数据库都可以包含自己的一组授权规则,这些规则指定允许哪些用户读写文档、创建设计文档以及更改某些数据库配置参数。授权规则由服务器管理员设置,可以随时修改。
数据库授权规则将用户分配到两个类之一:
- members ,允许阅读所有文档,创建和修改除设计文档以外的任何文档。
- admins ,可以读写所有类型的文档,修改哪些用户是成员或管理员,以及设置特定的每个数据库配置选项。
请注意,数据库管理员与服务器管理员不同——数据库管理员的操作仅限于特定的数据库。
第一次创建数据库时,没有成员或管理员。没有身份验证凭据或具有普通用户凭据的HTTP请求被视为成员,具有服务器管理员凭据的HTTP请求被视为数据库管理员。要更改默认权限,必须创建 _security 数据库中的文档:
> 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 文档必须包含服务器管理员的凭据。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" 到 names 阵列下 admin . 但是,跟踪单个数据库管理员用户名是很乏味的,因此您可能希望创建一个数据库管理员角色并将该角色分配给 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 document reference page 有关指定数据库成员和管理员的其他详细信息。