summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJason Smith (work) <jhs@nodejitsu.com>2013-05-31 18:06:25 +0000
committerJason Smith (work) <jhs@nodejitsu.com>2013-05-31 18:06:25 +0000
commitc98ba5612e313b252e5b7ac91b3772c226b82217 (patch)
tree5d2d5e0aa95493e5b0a0e3b52618b9d4fd1ed6cb
parentf15f54d56149d3119ca2c06bcd63991071fbbe40 (diff)
downloadcouchdb-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.rst26
-rw-r--r--share/www/script/test/config.js48
-rw-r--r--src/couchdb/couch_httpd_misc_handlers.erl42
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});