1.5. Security¶
In this document, we’ll look at the basic security mechanisms in CouchDB: Basic Authentication and Cookie Authentication. This is how CouchDB handles users and protects their credentials.
1.5.1. Authentication¶
CouchDB has the idea of an admin user (e.g. an administrator, a super user, or root) that is allowed to do anything to a CouchDB installation. By default, one admin user must be created for CouchDB to start up successfully.
CouchDB also defines a set of requests that only admin users are allowed to do. If you have defined one or more specific admin users, CouchDB will ask for identification for certain requests:
Creating a database (
PUT /database
)Deleting a database (
DELETE /database
)Setup a database security (
PUT /database/_security
)Creating a design document (
PUT /database/_design/app
)Updating a design document (
PUT /database/_design/app?rev=1-4E2
)Deleting a design document (
DELETE /database/_design/app?rev=2-6A7
)Triggering compaction (
POST /database/_compact
)Reading the task status list (
GET /_active_tasks
)Restarting the server on a given node (
POST /_node/{node-name}/_restart
)Reading the active configuration (
GET /_node/{node-name}/_config
)Updating the active configuration (
PUT /_node/{node-name}/_config/{section}/{key}
)
1.5.1.1. Creating a New Admin User¶
If your installation process did not set up an admin user, you will have to add
one to the configuration file by hand and restart CouchDB first. For the purposes of
this example, we’ll create a default admin
user with the password password
.
Warning
Don’t just type in the following without thinking! Pick a good name for your administrator user that isn’t easily guessable, and pick a secure password.
To the end of your etc/local.ini
file, after the [admins]
line, add the text
admin = password
, so it looks like this:
[admins]
admin = password
(Don’t worry about the password being in plain text; we’ll come back to this.)
Now, restart CouchDB using the method appropriate for your operating system. You should now be able to access CouchDB using your new administrator account:
> curl http://admin:password@127.0.0.1:5984/_up
{"status":"ok","seeds":{}}
Great!
Let’s create an admin user through the HTTP API. We’ll call her anna
, and
her password is secret
. Note the double quotes in the following code; they
are needed to denote a string value for the configuration API:
> HOST="http://admin:password@127.0.0.1:5984"
> NODENAME="_local"
> curl -X PUT $HOST/_node/$NODENAME/_config/admins/anna -d '"secret"'
""
As per the _config API’s behavior, we’re getting the previous value for the config item we just wrote. Since our admin user didn’t exist, we get an empty string.
Please note that _local
serves as an alias for the local node name, so for all
configuration URLs, NODENAME
may be set to _local
, to interact with the local
node’s configuration.
See also
1.5.1.1.1. Hashing Passwords¶
Seeing the plain-text password is scary, isn’t it? No worries, CouchDB doesn’t
show the plain-text password anywhere. It gets hashed right away. Go ahead and
look at your local.ini
file now. You’ll see that CouchDB has rewritten the
plain text passwords so they are hashed:
[admins]
admin = -pbkdf2-71c01cb429088ac1a1e95f3482202622dc1e53fe,226701bece4ae0fc9a373a5e02bf5d07,10
anna = -pbkdf2-2d86831c82b440b8887169bd2eebb356821d621b,5e11b9a9228414ab92541beeeacbf125,10
The hash is that big, ugly, long string that starts out with -pbkdf2-
.
To compare a plain-text password during authentication with the stored hash, the hashing algorithm is run and the resulting hash is compared to the stored hash. The probability of two identical hashes for different passwords is too insignificant to mention (c.f. Bruce Schneier). Should the stored hash fall into the hands of an attacker, it is, by current standards, way too inconvenient (i.e., it’d take a lot of money and time) to find the plain-text password from the hash.
When CouchDB starts up, it reads a set of .ini
files with config settings. It
loads these settings into an internal data store (not a database). The config
API lets you read the current configuration as well as change it and create new
entries. CouchDB writes any changes back to the .ini
files.
The .ini
files can also be edited by hand when CouchDB is not running.
Instead of creating the admin user as we showed previously, you could have
stopped CouchDB, opened your local.ini
, added anna = secret
to the
admins
, and restarted CouchDB. Upon reading the new line from
local.ini
, CouchDB would run the hashing algorithm and write back the hash
to local.ini
, replacing the plain-text password — just as it did for our
original admin
user. To make sure CouchDB only hashes plain-text passwords
and not an existing hash a second time, it prefixes the hash with -pbkdf2-
,
to distinguish between plain-text passwords and PBKDF2 hashed passwords. This
means your plain-text password can’t start with the characters -pbkdf2-
,
but that’s pretty unlikely to begin with.
1.5.1.2. Basic Authentication¶
CouchDB will not allow us to create new databases unless we give the correct admin user credentials. Let’s verify:
> HOST="http://127.0.0.1:5984"
> curl -X PUT $HOST/somedatabase
{"error":"unauthorized","reason":"You are not a server admin."}
That looks about right. Now we try again with the correct credentials:
> HOST="http://anna:secret@127.0.0.1:5984"
> curl -X PUT $HOST/somedatabase
{"ok":true}
If you have ever accessed a website or FTP server that was password-protected,
the username:password@
URL variant should look familiar.
If you are security conscious, the missing s
in http://
will make you
nervous. We’re sending our password to CouchDB in plain text. This is a bad
thing, right? Yes, but consider our scenario: CouchDB listens on 127.0.0.1
on a development box that we’re the sole user of. Who could possibly sniff our
password?
If you are in a production environment, however, you need to reconsider. Will your CouchDB instance communicate over a public network? Even a LAN shared with other collocation customers is public. There are multiple ways to secure communication between you or your application and CouchDB that exceed the scope of this documentation. CouchDB as of version 1.1.0 comes with SSL built in.
See also
1.5.2. Authentication Database¶
You may already note that CouchDB administrators are defined within the config
file and are wondering if regular users are also stored there. No, they are not.
CouchDB has a special authentication database, named _users
by default,
that stores all registered users as JSON documents.
This special database is a system database. This means that while it shares the common database API, there are some special security-related constraints applied. Below is a list of how the authentication database is different from the other databases.
Only administrators may browse list of all documents (
GET /_users/_all_docs
)Only administrators may listen to changes feed (
GET /_users/_changes
)Only administrators may execute design functions like views.
There is a special design document
_auth
that cannot be modifiedEvery document except the design documents represent registered CouchDB users and belong to them
By default, the
_security
settings of the_users
database disallow users from accessing or modifying documents
Note
Settings can be changed so that users do have access to the _users
database,
but even then they may only access (GET /_users/org.couchdb.user:Jan
) or modify (PUT /_users/org.couchdb.user:Jan
) documents that they own. This will not be possible in CouchDB 4.0.
These draconian rules are necessary since CouchDB cares about its users’ personal information and will not disclose it to just anyone. Often, user documents contain system information like login, password hash and roles, apart from sensitive personal information like real name, email, phone, special internal identifications and more. This is not information that you want to share with the World.
1.5.2.1. Users Documents¶
Each CouchDB user is stored in document format. These documents contain several mandatory fields, that CouchDB needs for authentication:
_id (string): Document ID. Contains user’s login with special prefix Why the org.couchdb.user: prefix?
derived_key (string): PBKDF2 key derived from prf/salt/iterations.
name (string): User’s name aka login. Immutable e.g. you cannot rename an existing user - you have to create new one
roles (array of string): List of user roles. CouchDB doesn’t provide any built-in roles, so you’re free to define your own depending on your needs. However, you cannot set system roles like
_admin
there. Also, only administrators may assign roles to users - by default all users have no rolespassword (string): A plaintext password can be provided, but will be replaced by hashed fields before the document is actually stored.
password_sha (string): Hashed password with salt. Used for
simple
password_schemepassword_scheme (string): Password hashing scheme. May be
simple
orpbkdf2
salt (string): Hash salt. Used for both
simple
andpbkdf2
password_scheme
options.iterations (integer): Number of iterations to derive key, used for
pbkdf2
password_scheme
See the configuration API:: for details.pbkdf2_prf (string): The PRF to use for
pbkdf2
. If missing,sha
is assumed. Can be any ofsha
,sha224
,sha256
,sha384
,sha512
.type (string): Document type. Constantly has the value
user
Additionally, you may specify any custom fields that relate to the target user.
1.5.2.1.1. Why the org.couchdb.user:
prefix?¶
The reason there is a special prefix before a user’s login name is to have namespaces that users belong to. This prefix is designed to prevent replication conflicts when you try merging two or more _user databases.
For current CouchDB releases, all users belong to the same
org.couchdb.user
namespace and this cannot be changed. This may be changed
in future releases.
1.5.2.2. Creating a New User¶
Creating a new user is a very trivial operation. You just need to do a PUT request with the user’s data to CouchDB. Let’s create a user with login jan and password apple:
curl -X PUT http://admin:password@localhost:5984/_users/org.couchdb.user:jan \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{"name": "jan", "password": "apple", "roles": [], "type": "user"}'
This curl command will produce the following HTTP request:
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
And CouchDB responds with:
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"}
The document was successfully created! The user jan should now exist in our database. Let’s check if this is true:
curl -X POST http://localhost:5984/_session -d 'name=jan&password=apple'
CouchDB should respond with:
{"ok":true,"name":"jan","roles":[]}
This means that the username was recognized and the password’s hash matches with the stored one. If we specify an incorrect login and/or password, CouchDB will notify us with the following error message:
{"error":"unauthorized","reason":"Name or password is incorrect."}
1.5.2.3. Password Changing¶
Let’s define what is password changing from the point of view of CouchDB and
the authentication database. Since “users” are “documents”, this operation is
just updating the document with a special field password
which contains
the plain text password. Scared? No need to be. The authentication database
has a special internal hook on document update which looks for this field and
replaces it with the secured hash depending on the chosen password_scheme
.
Summarizing the above process - we need to get the document’s content, add
the password
field with the new password in plain text and then store the
JSON result to the authentication database.
curl -X GET http://admin:password@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"
}
Here is our user’s document. We may strip hashes from the stored document to reduce the amount of posted data:
curl -X PUT http://admin:password@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"}
Updated! Now let’s check that the password was really changed:
curl -X POST http://localhost:5984/_session -d 'name=jan&password=apple'
CouchDB should respond with:
{"error":"unauthorized","reason":"Name or password is incorrect."}
Looks like the password apple
is wrong, what about orange
?
curl -X POST http://localhost:5984/_session -d 'name=jan&password=orange'
CouchDB should respond with:
{"ok":true,"name":"jan","roles":[]}
Hooray! You may wonder why this was so complex - we need to retrieve user’s document, add a special field to it, and post it back.
Note
There is no password confirmation for API request: you should implement it in your application layer.