1.7. Security
In this document, we’ll look at the basic security mechanisms in CouchDB: theAdmin Party, Basic Authentication, Cookie Authentication; how CouchDBhandles users and protects their credentials.
1.7.1. Authentication
1.7.1.1. The Admin Party
When you start out fresh, CouchDB allows any request to be made by anyone.Create a database? No problem, here you go. Delete some documents? Same deal.CouchDB calls this the Admin Party. Everybody has privileges to do anything.Neat.
While it is incredibly easy to get started with CouchDB that way,it should be obvious that putting a default installation into the wild isadventurous. Any rogue client could come along and delete a database.
A note of relief: by default, CouchDB will listen only on your loopbacknetwork interface (127.0.0.1
or localhost
) and thus only you will beable to make requests to CouchDB, nobody else. But when you start to open upyour CouchDB to the public (that is, by telling it to bind to your machine’spublic IP address), you will want to think about restricting access so thatthe next bad guy doesn’t ruin your admin party.
In our previous discussions, we dropped some keywords about how thingswithout the Admin Party work. First, there’s admin itself, which impliessome sort of super user. Then there are privileges. Let’s explore these termsa little more.
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,everybody is an admin. If you don’t like that, you can create specific adminusers with a username and password as their credentials.
CouchDB also defines a set of requests that only admin users are allowed todo. If you have defined one or more specific admin users, CouchDB will ask foridentification 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 <!--_restart-->
) - Reading the active configuration(
GET /_node/{node-name}/_config <!--_config-->
) - Updating the active configuration(
PUT /_node/{node-name}/_config/section/key <!--_config/{section}/{key}-->
)
1.7.1.1.1. Creating New Admin User
Let’s do another walk through the API using curl to see how CouchDB behaveswhen you add admin users.
- > HOST="http://127.0.0.1:5984"
- > curl -X PUT $HOST/database
- {"ok":true}
When starting out fresh, we can add a database. Nothing unexpected. Now let’screate an admin user. We’ll call her anna
, and her password is secret
.Note the double quotes in the following code; they are needed to denote a stringvalue for the configuration API:
- > curl -X PUT $HOST/_node/$NODENAME/_config/admins/anna -d '"secret"'
- ""
As per the _config API’s behavior, we’re gettingthe previous value for the config item we just wrote. Since our admin userdidn’t exist, we get an empty string.
1.7.1.1.2. Hashing Passwords
Seeing the plain-text password is scary, isn’t it? No worries, CouchDB doesn’tshow the plain-text password anywhere. It gets hashed right away. The hashis that big, ugly, long string that starts out with -hashed-
.How does that work?
- Creates a new 128-bit UUID. This is our salt.
- Creates a sha1 hash of the concatenation of the bytes of the plain-textpassword and the salt
(sha1(password + salt))
. - Prefixes the result with
-hashed-
and appends,salt
.
To compare a plain-text password during authentication with the stored hash,the same procedure is run and the resulting hash is compared to the storedhash. The probability of two identical hashes for different passwords is tooinsignificant to mention (c.f. Bruce Schneier). Should the stored hash fallinto 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 fromthe hash.
But what’s with the -hashed-
prefix? When CouchDB starts up, it reads a setof .ini files with config settings. It loads these settings into an internaldata store (not a database). The config API lets you read the currentconfiguration as well as change it and create new entries. CouchDB is writingany 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 havestopped CouchDB, opened your local.ini, added anna = secret
to theadmins
, and restarted CouchDB. Upon reading the new line fromlocal.ini, CouchDB would run the hashing algorithm and write back the hash tolocal.ini, replacing the plain-text password. To make sure CouchDB only hashesplain-text passwords and not an existing hash a second time, it prefixesthe hash with -hashed-
, to distinguish between plain-text passwords andhashed passwords. This means your plain-text password can’t start with thecharacters -hashed-
, but that’s pretty unlikely to begin with.
Note
Since 1.3.0 release CouchDB uses -pbkdf2-
prefixby default to sign about using PBKDF2 hashing algorithm instead ofSHA1.
1.7.1.2. Basic Authentication
Now that we have defined an admin, CouchDB will not allow us to create newdatabases unless we give the correct admin user credentials. Let’s verify:
- > 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 younervous. We’re sending our password to CouchDB in plain text. This is a badthing, 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 ourpassword?
If you are in a production environment, however, you need to reconsider. Willyour CouchDB instance communicate over a public network? Even a LAN sharedwith other collocation customers is public. There are multiple ways to securecommunication between you or your application and CouchDB that exceed thescope of this documentation. CouchDB as of version 1.1.0comes with SSL built in.
See also
Basic Authentication API Reference
1.7.1.3. Cookie Authentication
Basic authentication that uses plain-text passwords is nice and convenient,but not very secure if no extra measures are taken. It is also a very pooruser experience. If you use basic authentication to identify admins,your application’s users need to deal with an ugly, unstylable browser modaldialog that says non-professional at work more than anything else.
To remedy some of these concerns, CouchDB supports cookie authentication.With cookie authentication your application doesn’t have to include the uglylogin dialog that the users’ browsers come with. You can use a regular HTMLform to submit logins to CouchDB. Upon receipt, CouchDB will generate aone-time token that the client can use in its next request to CouchDB. WhenCouchDB sees the token in a subsequent request, it will authenticate the userbased on the token without the need to see the password again. By default,a token is valid for 10 minutes.
To obtain the first token and thus authenticate a user for the first time,the username and password must be sent to the _sessionAPI. The API is smart enough to decode HTML form submissions, so you don’t haveto resort to any smarts in your application.
If you are not using HTML forms to log in, you need to send an HTTP requestthat looks as if an HTML form generated it. Luckily, this is super simple:
- > 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 replies, and we’ll give you some more detail:
- < HTTP/1.1 200 OK
- < Set-Cookie: AuthSession=YW5uYTo0QUIzOTdFQjrC4ipN-D-53hw1sJepVzcVxnriEw;
- < Version=1; Path=/; HttpOnly
- > ...
- <
- {"ok":true}
A 200 OK response code tells us all is well, a Set-Cookieheader includes the token we can use for the next request, and the standard JSONresponse tells us again that the request was successful.
Now we can use this token to make another request as the same user withoutsending the username and password again:
- > 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}
You can keep using this token for 10 minutes by default. After 10 minutes youneed to authenticate your user again. The token lifetime can be configuredwith the timeout (in seconds) setting in the couch_httpd_auth configuration section.
See also
Cookie Authentication API Reference
1.7.2. Authentication Database
You may already note that CouchDB administrators are defined within the configfile 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 sharesthe common database API, there are somespecial security-related constraints applied. Below is a list of how theauthentication 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,shows and others
- There is a special design document
_auth
that cannot be modified - Every document except the design documents represent registeredCouchDB users and belong to them
- Users may only access (
GET /_users/org.couchdb.user:Jan
) or modify (PUT /_users/org.couchdb.user:Jan
) documents that they own
These draconian rules are necessary since CouchDB cares about its users’personal information and will not disclose it to just anyone. Often, userdocuments contain system information like login, password hash and roles,apart from sensitive personal information like real name, email, phone, specialinternal identifications and more. This is not information that youwant to share with the World.
1.7.2.1. Users Documents
Each CouchDB user is stored in document format. These documents containseveral mandatory fields, that CouchDB needs for authentication:
- _id (string): Document ID. Contains user’s login with special prefixWhy the org.couchdb.user: prefix?
- derived_key (string): PBKDF2 key
- name (string): User’s name aka login. Immutable e.g. you cannotrename an existing user - you have to create new one
- roles (array of string): List of user roles. CouchDB doesn’t provideany 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, onlyadministrators may assign roles to users - by default all users have no roles - password_sha (string): Hashed password with salt. Used for
simple
password_scheme - password_scheme (string): Password hashing scheme. May be
simple
orpbkdf2
- salt (string): Hash salt. Used for
simple
password_scheme - type (string): Document type. Constantly has the value
user
Additionally, you may specify any custom fields that relate to the targetuser. This is a good place to store user’s private information because only thetarget user and CouchDB administrators may browse it.
1.7.2.1.1. Why the org.couchdb.user: prefix?
The reason there is a special prefix before a user’s login name is to havenamespaces that users belong to. This prefix is designed to preventreplication conflicts when you try merging two or more _user databases.
For current CouchDB releases, all users belong to the sameorg.couchdb.user
namespace and this cannot be changed. This may be changedin future releases.
1.7.2.2. Creating a New User
Creating a new user is a very trivial operation. You just need to do aPUT request with the user’s data to CouchDB. Let’s create a user withlogin jan and password 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"}'
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 ourdatabase. 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 matcheswith the stored one. If we specify an incorrect login and/or password, CouchDBwill notify us with the following error message:
- {"error":"unauthorized","reason":"Name or password is incorrect."}
1.7.2.3. Password Changing
Let’s define what is password changing from the point of view of CouchDB andthe authentication database. Since “users” are “documents”, this operation isjust updating the document with a special field password
which containsthe plain text password. Scared? No need to be. The authentication databasehas a special internal hook on document update which looks for this field andreplaces it with the secured hash depending on the chosen password_scheme
.
Summarizing the above process - we need to get the document’s content, addthe password
field with the new password in plain text and then store theJSON result to the authentication database.
- 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"
- }
Here is our user’s document. We may strip hashes from the stored document toreduce the amount of posted data:
- 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"}
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’sdocument, add a special field to it, and post it back.
Note
There is no password confirmation for API request: you should implement itin your application layer.
1.7.2.4. Users Public Information
New in version 1.4.
Sometimes users want to share some information with the world. For instance,their contact email to let other users get in touch with them. To solve thisproblem, but still keep sensitive and private information secured, there isa special configuration option public_fields
. In this option you may definea comma-separated list of users document fields that will be publicly available.
Normally, if you request a user document and you’re not an administrator or thedocument’s owner, CouchDB will respond with 404 Not Found:
- curl http://localhost:5984/_users/org.couchdb.user:robert
- {"error":"not_found","reason":"missing"}
This response is constant for both cases when user exists or doesn’t exist forsecurity reasons.
Now let’s share the field name
. First, set up the publicfields
configuration option. Remember, that this action requires administratorprivileges. The next command will prompt you for user _admin’s password:
- curl -X PUT http://localhost:5984/_node/nonode@nohost/_config/couch_httpd_auth/public_fields \
- -H "Content-Type: application/json" \
- -d '"name"' \
- -u admin
What has changed? Let’s check Robert’s document once again:
- curl http://localhost:5984/_users/org.couchdb.user:robert
- {"_id":"org.couchdb.user:robert","_rev":"6-869e2d3cbd8b081f9419f190438ecbe7","name":"robert"}
Good news! Now we may read the field name
in every user document withoutneeding to be an administrator. Keep in mind, though, not to publish sensitiveinformation, especially without user’s consent!
1.7.3. Authorization
Now that you have a few users who can log in, you probably want to set up somerestrictions on what actions they can perform based on their identity and theirroles. Each database on a CouchDB server can contain its own set ofauthorization rules that specify which users are allowed to read and writedocuments, create design documents, and change certain database configurationparameters. The authorization rules are set up by a server admin and can bemodified at any time.
Database authorization rules assign a user into one of two classes:
- members, who are allowed to read all documents and create and modify anydocument except for design documents.
- admins, who can read and write all types of documents, modify which usersare members or admins, and set certain per-database configuration options.
Note that a database admin is not the same as a server admin – the actionsof a database admin are restricted to a specific database.
When a database is first created, there are no members or admins. HTTPrequests that have no authentication credentials or have credentials for anormal user are treated as members, and those with server admin credentialsare treated as database admins. To change the default permissions, you mustcreate a _security document in the database:
- > curl -X PUT http://localhost:5984/mydatabase/_security \
- -u anna:secret \
- -H "Content-Type: application/json" \
- -d '{"admins": { "names": [], "roles": [] }, "members": { "names": ["jan"], "roles": [] } }'
The HTTP request to create the _security document must contain thecredentials of a server admin. CouchDB will respond with:
- {"ok":true}
The database is now secured against anonymous reads and writes:
- > curl http://localhost:5984/mydatabase/
- {"error":"unauthorized","reason":"You are not authorized to access this db."}
You declared user “jan” as a member in this database, so he is able to read andwrite normal documents:
- > 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,"disk_size":12376,"data_size":272,"instance_start_time":"0",
- "disk_format_version":6,"committed_update_seq":3}
If Jan attempted to create a design doc, however, CouchDB would return a401 Unauthorized error because the username “jan” is not in the list ofadmin names and the /_users/org.couchdb.user:jan document doesn’t containa role that matches any of the declared admin roles. If you want to promoteJan to an admin, you can update the security document to add “jan” tothe names array under admin. Keeping track of individual databaseadmin usernames is tedious, though, so you would likely prefer to create adatabase admin role and assign that role to the org.couchdb.user:jan userdocument:
- > 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": [] } }'
See the _security document reference page foradditional details about specifying database members and admins.