diff options
author | Jason Smith (work) <jhs@nodejitsu.com> | 2013-05-31 18:06:25 +0000 |
---|---|---|
committer | Jason Smith (work) <jhs@nodejitsu.com> | 2013-05-31 18:06:25 +0000 |
commit | c98ba5612e313b252e5b7ac91b3772c226b82217 (patch) | |
tree | 5d2d5e0aa95493e5b0a0e3b52618b9d4fd1ed6cb | |
parent | f15f54d56149d3119ca2c06bcd63991071fbbe40 (diff) | |
download | couchdb-c98ba5612e313b252e5b7ac91b3772c226b82217.tar.gz |
Allow storing a pre-hashed admin password
When duplicating a couch, it is difficult to copy the _config/admins/*
values. Storing the encoded value does not work because that value is
re-hashed when stored. (Your password is the literal string
"-pbkdf2-abcdef...".)
This change will store any config setting unmodified if ?raw=true is
in the query string.
Updating _config/admins/* already requires admin privileges, so there is
no change to the security.
-rw-r--r-- | share/doc/src/configuring.rst | 26 | ||||
-rw-r--r-- | share/www/script/test/config.js | 48 | ||||
-rw-r--r-- | src/couchdb/couch_httpd_misc_handlers.erl | 42 |
3 files changed, 109 insertions, 7 deletions
diff --git a/share/doc/src/configuring.rst b/share/doc/src/configuring.rst index 4b8bb11c9..8d3e70452 100644 --- a/share/doc/src/configuring.rst +++ b/share/doc/src/configuring.rst @@ -240,6 +240,32 @@ supports querying, deleting or creating new admin accounts: "architect": "-pbkdf2-43ecbd256a70a3a2f7de40d2374b6c3002918834,921a12f74df0c1052b3e562a23cd227f,10000" } +If you already have a salted, encrypted password string (for example, +from an old ``local.ini`` file, or from a different CouchDB server), then +you can store the "raw" encrypted string, without having CouchDB doubly +encrypt it. + +.. code-block:: bash + + shell> PUT /_config/admins/architect?raw=true HTTP/1.1 + Accept: application/json + Content-Type: application/json + Content-Length: 89 + Host: localhost:5984 + + "-pbkdf2-43ecbd256a70a3a2f7de40d2374b6c3002918834,921a12f74df0c1052b3e562a23cd227f,10000" + + HTTP/1.1 200 OK + Cache-Control: must-revalidate + Content-Length: 89 + Content-Type: application/json + Date: Fri, 30 Nov 2012 11:39:18 GMT + Server: CouchDB/1.3.0 (Erlang OTP/R15B02) + +.. code-block:: json + + "-pbkdf2-43ecbd256a70a3a2f7de40d2374b6c3002918834,921a12f74df0c1052b3e562a23cd227f,10000" + Further details are available in ``security_``, including configuring the work factor for ``PBKDF2``, and the algorithm itself at `PBKDF2 (RFC-2898) <http://tools.ietf.org/html/rfc2898>`_. diff --git a/share/www/script/test/config.js b/share/www/script/test/config.js index 5382778f6..193aa89bc 100644 --- a/share/www/script/test/config.js +++ b/share/www/script/test/config.js @@ -72,6 +72,54 @@ couchTests.config = function(debug) { config = JSON.parse(xhr.responseText); T(config == "bar"); + // Server-side password hashing, and raw updates disabling that. + var password_plain = 's3cret'; + var password_hashed = null; + + xhr = CouchDB.request("PUT", "/_config/admins/administrator",{ + body : JSON.stringify(password_plain), + headers: {"X-Couch-Persist": "false"} + }); + TEquals(200, xhr.status, "Create an admin in the config"); + + T(CouchDB.login("administrator", password_plain).ok); + + xhr = CouchDB.request("GET", "/_config/admins/administrator"); + password_hashed = JSON.parse(xhr.responseText); + T(password_hashed.match(/^-pbkdf2-/) || password_hashed.match(/^-hashed-/), + "Admin password is hashed"); + + xhr = CouchDB.request("PUT", "/_config/admins/administrator?raw=nothanks",{ + body : JSON.stringify(password_hashed), + headers: {"X-Couch-Persist": "false"} + }); + TEquals(400, xhr.status, "CouchDB rejects an invalid 'raw' option"); + + xhr = CouchDB.request("PUT", "/_config/admins/administrator?raw=true",{ + body : JSON.stringify(password_hashed), + headers: {"X-Couch-Persist": "false"} + }); + TEquals(200, xhr.status, "Set an raw, pre-hashed admin password"); + + xhr = CouchDB.request("PUT", "/_config/admins/administrator?raw=false",{ + body : JSON.stringify(password_hashed), + headers: {"X-Couch-Persist": "false"} + }); + TEquals(200, xhr.status, "Set an admin password with raw=false"); + + // The password is literally the string "-pbkdf2-abcd...". + T(CouchDB.login("administrator", password_hashed).ok); + + xhr = CouchDB.request("GET", "/_config/admins/administrator"); + T(password_hashed != JSON.parse(xhr.responseText), + "Hashed password was not stored as a raw string"); + + xhr = CouchDB.request("DELETE", "/_config/admins/administrator",{ + headers: {"X-Couch-Persist": "false"} + }); + TEquals(200, xhr.status, "Delete an admin from the config"); + T(CouchDB.logout().ok); + // Non-term whitelist values allow further modification of the whitelist. xhr = CouchDB.request("PUT", "/_config/httpd/config_whitelist",{ body : JSON.stringify("!This is an invalid Erlang term!"), diff --git a/src/couchdb/couch_httpd_misc_handlers.erl b/src/couchdb/couch_httpd_misc_handlers.erl index d1f947d18..96a05c6d6 100644 --- a/src/couchdb/couch_httpd_misc_handlers.erl +++ b/src/couchdb/couch_httpd_misc_handlers.erl @@ -219,13 +219,35 @@ handle_config_req(Req) -> % PUT /_config/Section/Key % "value" -handle_approved_config_req(#httpd{method='PUT', path_parts=[_, Section, Key]}=Req, Persist) -> - Value = case Section of - <<"admins">> -> - couch_passwords:hash_admin_password(couch_httpd:json_body(Req)); - _ -> - couch_httpd:json_body(Req) +handle_approved_config_req(Req, Persist) -> + Query = couch_httpd:qs(Req), + UseRawValue = case lists:keyfind("raw", 1, Query) of + false -> false; % Not specified + {"raw", ""} -> false; % Specified with no value, i.e. "?raw" and "?raw=" + {"raw", "false"} -> false; + {"raw", "true"} -> true; + {"raw", InvalidValue} -> InvalidValue + end, + handle_approved_config_req(Req, Persist, UseRawValue). + +handle_approved_config_req(#httpd{method='PUT', path_parts=[_, Section, Key]}=Req, + Persist, UseRawValue) + when UseRawValue =:= false orelse UseRawValue =:= true -> + RawValue = couch_httpd:json_body(Req), + Value = case UseRawValue of + true -> + % Client requests no change to the provided value. + RawValue; + false -> + % Pre-process the value as necessary. + case Section of + <<"admins">> -> + couch_passwords:hash_admin_password(RawValue); + _ -> + RawValue + end end, + OldValue = couch_config:get(Section, Key, ""), case couch_config:set(Section, Key, ?b2l(Value), Persist) of ok -> @@ -233,8 +255,14 @@ handle_approved_config_req(#httpd{method='PUT', path_parts=[_, Section, Key]}=Re Error -> throw(Error) end; + +handle_approved_config_req(#httpd{method='PUT'}=Req, _Persist, UseRawValue) -> + Err = io_lib:format("Bad value for 'raw' option: ~s", [UseRawValue]), + send_json(Req, 400, {[{error, ?l2b(Err)}]}); + % DELETE /_config/Section/Key -handle_approved_config_req(#httpd{method='DELETE',path_parts=[_,Section,Key]}=Req, Persist) -> +handle_approved_config_req(#httpd{method='DELETE',path_parts=[_,Section,Key]}=Req, + Persist, _UseRawValue) -> case couch_config:get(Section, Key, null) of null -> throw({not_found, unknown_config_value}); |