diff options
author | Alexander Shorin <kxepal@apache.org> | 2013-09-28 15:45:14 +0400 |
---|---|---|
committer | Alexander Shorin <kxepal@apache.org> | 2013-09-28 15:45:14 +0400 |
commit | d513c57619251d4b7b0023a7f2b86b69b4a11300 (patch) | |
tree | 0710afe30b277ad127422d2aa839193b08467de1 | |
parent | 5522488e8c2e0772aa5f2844d0c557f3f17c5586 (diff) | |
download | couchdb-d513c57619251d4b7b0023a7f2b86b69b4a11300.tar.gz |
Import Security chapter from Guide to CouchDB. Describe _users db.
TODO: Authorization and Database Security parts.
COUCHDB-1822
-rw-r--r-- | share/doc/build/Makefile.am | 3 | ||||
-rw-r--r-- | share/doc/src/intro/index.rst | 1 | ||||
-rw-r--r-- | share/doc/src/intro/security.rst | 525 |
3 files changed, 529 insertions, 0 deletions
diff --git a/share/doc/build/Makefile.am b/share/doc/build/Makefile.am index 870976a15..ef98bde49 100644 --- a/share/doc/build/Makefile.am +++ b/share/doc/build/Makefile.am @@ -115,6 +115,7 @@ html_files = \ html/_sources/intro/futon.txt \ html/_sources/intro/index.txt \ html/_sources/intro/overview.txt \ + html/_sources/intro/security.txt \ html/_sources/intro/tour.txt \ html/_sources/intro/why.txt \ html/_sources/maintenance/compaction.txt \ @@ -234,6 +235,7 @@ html_files = \ html/intro/futon.html \ html/intro/index.html \ html/intro/overview.html \ + html/intro/security.html \ html/intro/tour.html \ html/intro/why.html \ html/maintenance/compaction.html \ @@ -375,6 +377,7 @@ src_files = \ ../src/intro/futon.rst \ ../src/intro/index.rst \ ../src/intro/overview.rst \ + ../src/intro/security.rst \ ../src/intro/tour.rst \ ../src/intro/why.rst \ ../src/maintenance/compaction.rst \ diff --git a/share/doc/src/intro/index.rst b/share/doc/src/intro/index.rst index a934fae21..1c1088bef 100644 --- a/share/doc/src/intro/index.rst +++ b/share/doc/src/intro/index.rst @@ -51,5 +51,6 @@ teach how to use CouchDB. consistency tour api + security futon curl diff --git a/share/doc/src/intro/security.rst b/share/doc/src/intro/security.rst new file mode 100644 index 000000000..1c2515973 --- /dev/null +++ b/share/doc/src/intro/security.rst @@ -0,0 +1,525 @@ +.. Licensed under the Apache License, Version 2.0 (the "License"); you may not +.. use this file except in compliance with the License. You may obtain a copy of +.. the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +.. WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +.. License for the specific language governing permissions and limitations under +.. the License. + + +.. _intro/security: + +******** +Security +******** + +In this chapter, we'll look at the basic security mechanisms in CouchDB: the +`Admin Party`, `Basic Authentication`, `Cookie Authentication`; how CouchDB +handles users and protects their credentials. + +============== +Authentication +============== + +.. _intro/security/admin_party: + +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 is +adventurous. Any rogue client could come along and delete a database. + +A note of relief: by default, CouchDB will listen only on your loopback +network interface (``127.0.0.1`` or ``localhost``) and thus only you will be +able to make requests to CouchDB, nobody else. But when you start to open up +your CouchDB to the public (that is, by telling it to bind to your machine's +public IP address), you will want to think about restricting access so that +the next bad guy doesn't ruin your admin party. + +In our previous discussions, we dropped some keywords about how things +without the `Admin Party` work. First, there's *admin* itself, which implies +some sort of super user. Then there are *privileges*. Let's explore these terms +a 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 admin +users with a username and password as their credentials. + +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:`PUT /database </{db}>`) +- Deleting a database (:put:`DELETE /database </{db}>`) +- Setup a database security (:put:`PUT /database/_security + </{db}/_security>`) +- Creating a design document (:put:`PUT /database/_design/app + </{db}/_design/{ddocname}>`) +- Updating a design document (:put:`PUT /database/_design/app?rev=1-4E2 + </{db}/_design/{ddocname}>`) +- Deleting a design document (:delete:`DELETE /database/_design/app?rev=2-6A7 + </{db}/_design/{ddocname}>`) +- Execute a temporary view (:post:`POST /database/_temp_view + </{db}/_temp_view>`) +- Triggering compaction (:post:`POST /database/_compact </{db}/_compact>`) +- Reading the task status list (:get:`GET /_active_tasks </_active_tasks>`) +- Restarting the server (:post:`:POST /_restart </_restart>`) +- Reading the active configuration (:get:`GET /_config </_config>`) +- Updating the active configuration (:put:`PUT /_config/section/key + </_config/{section}/{key}>`) + + +Creating New Admin User +----------------------- + +Let's do another walk through the API using `curl` to see how CouchDB behaves +when 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's +create 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 string +value for the :ref:`configuration API <api/config>`:: + + > curl -X PUT $HOST/_config/admins/anna -d '"secret"' + "" + +As per the :ref:`_config <api/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. + + +Hashing Passwords +----------------- + +Seeing the plain-text password is scary, isn't it? No worries, CouchDB doesn't +show up the plain-text password anywhere. It gets hashed right away. The hash +is 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-text + password 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 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. + +.. _Bruce Schneier: http://en.wikipedia.org/wiki/Bruce_Schneier + +But what's with the ``-hashed-`` prefix? 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 is writing +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 +:ref:`[admins] section <config/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. To make sure +CouchDB only hashes plain-text passwords and not an existing hash a second time, +it prefixes the hash with ``-hashed-``, to distinguish between plain-text +passwords and hashed passwords. This means your plain-text password can't start +with the characters ``-hashed-``, but that's pretty unlikely to begin with. + +.. note:: + + Since :ref:`1.3.0 release <release/1.3.0>` CouchDB uses ``-pbkdf2-`` prefix + by default to sign about using `PBKDF2`_ hashing algorithm instead of `SHA1`. + + .. _PBKDF2: http://en.wikipedia.org/wiki/PBKDF2 + + +.. _intro/security/basicauth: + +Basic Authentication +==================== + +Now that we have defined an admin, CouchDB will not allow us to create new +databases 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 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 :ref:`1.1.0 <release/1.1.0>` +comes with :ref:`SSL built in <config/ssl>`. + +.. seealso:: + + :ref:`Basic Authentication API Reference <api/auth/basic>` + + +.. _intro/security/cookie: + +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 poor +user experience. If you use basic authentication to identify admins, +your application's users need to deal with an ugly, unstylable browser modal +dialog 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 ugly +login dialog that the users' browsers come with. You can use a regular HTML +form to submit logins to CouchDB. Upon receipt, CouchDB will generate a +one-time token that the client can use in its next request to CouchDB. When +CouchDB sees the token in a subsequent request, it will authenticate the user +based 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 :ref:`_session <api/auth/session>` +API. The API is smart enough to decode HTML form submissions, so you don't have +to resort to any smarts in your application. + +If you are not using HTML forms to log in, you need to send an HTTP request +that 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 :statuscode:`200` response code tells us all is well, a :header:`Set-Cookie` +header includes the token we can use for the next request, and the standard JSON +response tells us again that the request was successful. + +Now we can use this token to make another request as the same user without +sending 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 you +need to authenticate your user again. The token lifetime can be configured +with the timeout (in seconds) setting in the :ref:`couch_httpd_auth +<config/couch_httpd_auth>` configuration section. + +.. seealso:: + + :ref:`Cookie Authentication API Reference <api/auth/cookie>` + + +======================= +Authentication Database +======================= + +You may already note, that CouchDB administrators are defined within config file +and you now wondering does regular users are also stored there. No, they don't. +CouchDB has special `authentication database` -- ``_users`` by default -- that +stores all registered users as JSON documents. + +CouchDB uses special database (called ``_users`` by default) to store +information about registered users. This is a `system database` -- this means +that while it shares common :ref:`database API <api/database>`, there are some +special security-related constraints applied and used agreements on documents +structure. So how `authentication database` is different from others? + +- Only administrators may browse list of all documents + (:get:`GET /_users/_all_docs </{db}/_all_docs>`) +- Only administrators may listen :ref:`changes feed + <changes>` (:get:`GET /_users/_changes </{db}/_changes>`) +- Only administrators may execute design functions like :ref:`views <viewfun>`, + :ref:`shows <showfun>` and :ref:`others <ddocs>` +- Only administrators may :method:`GET`, :method:`PUT` or :method:`DELETE` + any document (to be honest, that they always can do) +- There is special design document ``_auth`` that cannot be modified +- Every document (of course, except `design documents`) represents registered + CouchDB users and belong to him +- Users may only access (:get:`GET /_users/org.couchdb.user:Jan + </{db}/{docid}>`) or modify (:put:`PUT /_users/org.couchdb.user:Jan + </{db}/{docid}>`) documents that they owns + +These draconian rules are reasonable: CouchDB cares about user's personal +information and doesn't discloses it for everyone. Often, users documents are +contains not only system information like `login`, `password hash` and `roles`, +but also sensitive personal information like: real name, email, phone, special +internal identifications and more - this is not right information that you +want to share with the World. + + +Users Documents +=============== + +Each CouchDB user is stored in document format. These documents are contains +several *mandatory* fields, that CouchDB handles for correct authentication +process: + +- **_id** (*string*): Document ID. Contains user's login with special prefix + :ref:`org.couchdb.user` +- **derived_key** (*string*): `PBKDF2`_ key +- **name** (*string*): User's name aka login. **Immutable** e.g. you cannot + rename existed user - you have to create new one +- **roles** (*array* of *string*): List of user roles. CouchDB doesn't provides + any builtin 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 roles +- **password_sha** (*string*): Hashed password with salt. Used for ``simple`` + `password_scheme` +- **password_scheme** (*string*): Password hashing scheme. May be ``simple`` or + ``pbkdf2`` +- **salt** (*string*): Hash salt. Used for ``simple`` `password_scheme` +- **type** (*string*): Document type. Constantly have value ``user`` + +Additionally, you may specify any custom fields that are relates to the target +user. This is good place to store user's private information because only he and +CouchDB administrators may browse it. + +.. _org.couchdb.user: + +Why ``org.couchdb.user:`` prefix? +--------------------------------- + +The reason to have special prefix before user's login name is to have +namespaces which users are belongs to. This prefix is designed to prevent +replication conflicts when you'll try to merge two `_user` databases or more. + +For current CouchDB releases, all users are belongs to the same +``org.couchdb.user`` namespace and this cannot be changed, but we'd made +such design decision for future releases. + + +Creating New User +================= + +Creating new user is a very trivial operation. You need just to send single +:method:`PUT` request with user's data to CouchDB. Let's create user with login +`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 next HTTP request: + +.. code-block:: 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 + +And CouchDB responds with: + +.. code-block:: http + + 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"} + +Document successfully created what also means that user `jan` have created too! +Let's check is this true:: + + curl -X POST http://localhost:5984/_session -d 'name=jan&password=apple' + +CouchDB should respond with: + +.. code-block:: javascript + + {"ok":true,"name":"jan","roles":[]} + +Which means that username was recognized and password's hash matches with stored +one. If we specify wrong login and/or password, CouchDB will notify us with +the next error message: + +.. code-block:: javascript + + {"error":"unauthorized","reason":"Name or password is incorrect."} + + +Password Changing +================= + +This is quite common situation: user had forgot his password, it was leaked +somehow (via copy-paste, screenshot, or by typing in wrong chat window) or +something else. Let's change password for our user `jan`. + +First of all, let's define what is the password changing from the point of +CouchDB and the authentication database. Since "users" are "documents", this +operation is nothing, but updating the document with special field ``password`` +which contains the *plain text password*. Scared? No need to: the authentication +database has special internal hook on document update which looks for this +field and replaces it with the *secured hash*, depending on chosen +``password_scheme``. + +Summarizing above, we need to get document content, add ``password`` field +with new plain text password value and store JSON result to the authentication +database. + +:: + + curl -X GET http://localhost:5984/_users/org.couchdb.user:jan + +.. code-block:: javascript + + { + "_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 stored document to reduce +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"}' + +.. code-block:: javascript + + {"ok":true,"id":"org.couchdb.user:jan","rev":"2-ed293d3a0ae09f0c624f10538ef33c6f"} + +Updated! Now let's check that password was really changed:: + + curl -X POST http://localhost:5984/_session -d 'name=jan&password=apple' + +CouchDB should respond with: + +.. code-block:: javascript + + {"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: + +.. code-block:: javascript + + {"ok":true,"name":"jan","roles":[]} + +Hooray! You may wonder why so complex: need to retrieve user's document, add +special field to him, post it back - where is one big button that changes the +password without worry about document's content? Actually, :ref:`Futon +<intro/futon>` has such at the right bottom corner if you have logged in - +all implementation details are hidden from your sight. + +.. note:: + + There is no password confirmation for API request: you should implement it + on your application layer like Futon does. + + +Users Public Information +======================== + +.. versionadded:: 1.4 + +Sometimes users *wants* to share some information with the World. For instance, +their contact email to let other users get in touch with him. To solve this +problem, but still keep sensitive and private information secured there is +special :ref:`configuration <config>` option :ref:`public_fields +<config/couch_httpd_auth/public_fields>`. In this options you may define comma +separated list of users document fields that will be publicity available. + +Normally, if you request any user's document and you're not administrator or +this document owner, CouchDB will respond with :statuscode:`404`:: + + curl http://localhost:5984/_users/org.couchdb.user:robert + +.. code-block:: javascript + + {"error":"not_found","reason":"missing"} + +This response is constant for both cases when user exists or not exists - by +security reasons. + +Now let's share field ``name``. First, setup the ``public_fields`` configuration +option. Remember, that this action requires administrator's privileges and +the next command will ask for password for user `admin`, assuming that he is +the server administrator:: + + curl -X PUT http://localhost:5984/_config/couch_http_auth/public_fields \ + -H "Content-Type: application/json" \ + -d '"name"' \ + -u admin + +What have changed? Let's check Robert's document once again:: + + curl http://localhost:5984/_users/org.couchdb.user:robert + +.. code-block:: javascript + + {"_id":"org.couchdb.user:robert","_rev":"6-869e2d3cbd8b081f9419f190438ecbe7","name":"robert"} + +Good news! Now we may read field ``name`` from *every user's document without +need to be an administrator*. That's important note: don't publish sensitive +information, especially without user's acknowledge - they may not like such +actions from your side. |