diff options
author | Kristina <kristina@10gen.com> | 2010-12-27 16:05:40 -0500 |
---|---|---|
committer | Kristina <kristina@10gen.com> | 2010-12-27 16:07:33 -0500 |
commit | fbf7b90a1eac4b93180ecc2cf02ae8daadc5d7b3 (patch) | |
tree | d921ef4c2b85babac895d16b045d8187a7d58311 | |
parent | 9a8dda83d13938e981194a87a5e60bdf2d623f50 (diff) | |
download | mongo-fbf7b90a1eac4b93180ecc2cf02ae8daadc5d7b3.tar.gz |
rs auth
-rw-r--r-- | SConstruct | 2 | ||||
-rw-r--r-- | db/cmdline.cpp | 14 | ||||
-rw-r--r-- | db/repl.cpp | 45 | ||||
-rw-r--r-- | db/repl/connections.h | 11 | ||||
-rw-r--r-- | db/repl/rs.cpp | 3 | ||||
-rw-r--r-- | db/repl/rs_sync.cpp | 3 | ||||
-rw-r--r-- | db/security.cpp | 4 | ||||
-rw-r--r-- | db/security.h | 4 | ||||
-rw-r--r-- | db/security_commands.cpp | 37 | ||||
-rw-r--r-- | db/security_key.cpp | 103 | ||||
-rw-r--r-- | db/security_key.h | 47 | ||||
-rw-r--r-- | jstests/replsets/auth1.js | 181 | ||||
-rw-r--r-- | jstests/replsets/key1 | 1 | ||||
-rw-r--r-- | jstests/replsets/key2 | 1 |
14 files changed, 412 insertions, 44 deletions
diff --git a/SConstruct b/SConstruct index 10fe992a590..26d8556afde 100644 --- a/SConstruct +++ b/SConstruct @@ -302,7 +302,7 @@ if has_option( "full" ): # ------ SOURCE FILE SETUP ----------- -commonFiles = Split( "pch.cpp buildinfo.cpp db/common.cpp db/indexkey.cpp db/jsobj.cpp bson/oid.cpp db/json.cpp db/lasterror.cpp db/nonce.cpp db/queryutil.cpp db/projection.cpp shell/mongo.cpp" ) +commonFiles = Split( "pch.cpp buildinfo.cpp db/common.cpp db/indexkey.cpp db/jsobj.cpp bson/oid.cpp db/json.cpp db/lasterror.cpp db/nonce.cpp db/queryutil.cpp db/projection.cpp shell/mongo.cpp db/security_key.cpp" ) commonFiles += [ "util/background.cpp" , "util/mmap.cpp" , "util/sock.cpp" , "util/util.cpp" , "util/message.cpp" , "util/assert_util.cpp" , "util/log.cpp" , "util/httpclient.cpp" , "util/md5main.cpp" , "util/base64.cpp", "util/concurrency/vars.cpp", "util/concurrency/task.cpp", "util/debug_util.cpp", "util/concurrency/thread_pool.cpp", "util/password.cpp", "util/version.cpp", "util/signal_handlers.cpp", diff --git a/db/cmdline.cpp b/db/cmdline.cpp index ce596647796..d65a54fcf0c 100644 --- a/db/cmdline.cpp +++ b/db/cmdline.cpp @@ -20,6 +20,7 @@ #include "cmdline.h" #include "commands.h" #include "../util/processinfo.h" +#include "security_key.h" namespace po = boost::program_options; @@ -47,6 +48,7 @@ namespace mongo { ("logpath", po::value<string>() , "log file to send write to instead of stdout - has to be a file, not directory" ) ("logappend" , "append to logpath instead of over-writing" ) ("pidfilepath", po::value<string>(), "full path to pidfile (if not set, no pidfile is created)") + ("keyFile", po::value<string>(), "private key for cluster authentication (only for replica sets)") #ifndef _WIN32 ("fork" , "fork server process" ) #endif @@ -223,6 +225,18 @@ namespace mongo { writePidFile( params["pidfilepath"].as<string>() ); } + if (params.count("keyFile")){ + const string f = params["keyFile"].as<string>(); + + if (!setUpSecurityKey(f)) { + // error message printed in setUpPrivateKey + dbexit(EXIT_BADOPTIONS); + } + + noauth = false; + } + + { BSONArrayBuilder b; for (int i=0; i < argc; i++) diff --git a/db/repl.cpp b/db/repl.cpp index c9a9e5b1888..14334a86be8 100644 --- a/db/repl.cpp +++ b/db/repl.cpp @@ -1431,27 +1431,30 @@ namespace mongo { return false; } - BSONObj user; - { - dblock lk; - Client::Context ctxt("local."); - if( !Helpers::findOne("local.system.users", userReplQuery, user) ) { - // try the first user is local - if( !Helpers::getSingleton("local.system.users", user) ) { - if( noauth ) - return true; // presumably we are running a --noauth setup all around. - - log() << "replauthenticate: no user in local.system.users to use for authentication\n"; - return false; - } - } - - } - - string u = user.getStringField("user"); - string p = user.getStringField("pwd"); - massert( 10392 , "bad user object? [1]", !u.empty()); - massert( 10393 , "bad user object? [2]", !p.empty()); + string u; + string p; + if (internalSecurity.pwd.length() > 0) { + u = internalSecurity.user; + p = internalSecurity.pwd; + } + else { + BSONObj user; + { + dblock lk; + Client::Context ctxt("local."); + if( !Helpers::findOne("local.system.users", userReplQuery, user) || + // try the first user in local + !Helpers::getSingleton("local.system.users", user) ) { + log() << "replauthenticate: no user in local.system.users to use for authentication\n"; + return noauth; + } + } + u = user.getStringField("user"); + p = user.getStringField("pwd"); + massert( 10392 , "bad user object? [1]", !u.empty()); + massert( 10393 , "bad user object? [2]", !p.empty()); + } + string err; if( !conn->auth("local", u.c_str(), p.c_str(), err, false) ) { log() << "replauthenticate: can't authenticate to master server, user:" << u << endl; diff --git a/db/repl/connections.h b/db/repl/connections.h index 682aea4afdf..428b8302491 100644 --- a/db/repl/connections.h +++ b/db/repl/connections.h @@ -20,6 +20,7 @@ #include <map> #include "../../client/dbclient.h" +#include "../security_key.h" namespace mongo { @@ -96,7 +97,15 @@ namespace mongo { // we already locked above... string err; - x->cc.connect(hostport, err); + if (!x->cc.connect(hostport, err)) { + log() << "couldn't connect to " << hostport << ": " << err << rsLog; + return; + } + + if (!noauth && !x->cc.auth("local", internalSecurity.user, internalSecurity.pwd, err, false)) { + log() << "could not authenticate against " << conn()->toString() << ", " << err << rsLog; + return; + } } inline ScopedConn::~ScopedConn() { diff --git a/db/repl/rs.cpp b/db/repl/rs.cpp index 88d66c2b47b..ed0eb3b18c5 100644 --- a/db/repl/rs.cpp +++ b/db/repl/rs.cpp @@ -645,6 +645,9 @@ namespace mongo { assert(!replSet); return; } + if( !noauth ) { + cc().getAuthenticationInfo()->authorize("local"); + } (theReplSet = new ReplSet(*replSetCmdline))->go(); } catch(std::exception& e) { diff --git a/db/repl/rs_sync.cpp b/db/repl/rs_sync.cpp index 80ed4a8875b..0c0811fdf4a 100644 --- a/db/repl/rs_sync.cpp +++ b/db/repl/rs_sync.cpp @@ -493,6 +493,9 @@ namespace mongo { Client::initThread("replica set sync"); cc().iAmSyncThread(); + if (!noauth) { + cc().getAuthenticationInfo()->authorize("local"); + } theReplSet->syncThread(); cc().shutdown(); } diff --git a/db/security.cpp b/db/security.cpp index 9bb1a700b1c..2b43e6798c7 100644 --- a/db/security.cpp +++ b/db/security.cpp @@ -26,9 +26,7 @@ namespace mongo { - bool noauth = true; - - int AuthenticationInfo::warned = 0; + int AuthenticationInfo::warned = 0; void AuthenticationInfo::print(){ cout << "AuthenticationInfo: " << this << '\n'; diff --git a/db/security.h b/db/security.h index a6a91035cb5..577af9b434a 100644 --- a/db/security.h +++ b/db/security.h @@ -20,12 +20,10 @@ #include "nonce.h" #include "concurrency.h" +#include "security_key.h" namespace mongo { - // --noauth cmd line option - extern bool noauth; - /* for a particular db */ struct Auth { Auth() { level = 0; } diff --git a/db/security_commands.cpp b/db/security_commands.cpp index 30f8a8c6104..19ebc55b93f 100644 --- a/db/security_commands.cpp +++ b/db/security_commands.cpp @@ -133,27 +133,34 @@ namespace mongo { return false; } } - - static BSONObj userPattern = fromjson("{\"user\":1}"); - string systemUsers = dbname + ".system.users"; - OCCASIONALLY Helpers::ensureIndex(systemUsers.c_str(), userPattern, false, "user_1"); - + BSONObj userObj; - { - BSONObjBuilder b; - b << "user" << user; - BSONObj query = b.done(); - if( !Helpers::findOne(systemUsers.c_str(), query, userObj) ) { - log() << "auth: couldn't find user " << user << ", " << systemUsers << endl; - errmsg = "auth fails"; - return false; + string pwd; + + if (user == internalSecurity.user) { + pwd = internalSecurity.pwd; + } + else { + static BSONObj userPattern = fromjson("{\"user\":1}"); + string systemUsers = dbname + ".system.users"; + OCCASIONALLY Helpers::ensureIndex(systemUsers.c_str(), userPattern, false, "user_1"); + { + BSONObjBuilder b; + b << "user" << user; + BSONObj query = b.done(); + if( !Helpers::findOne(systemUsers.c_str(), query, userObj) ) { + log() << "auth: couldn't find user " << user << ", " << systemUsers << endl; + errmsg = "auth fails"; + return false; + } } + + pwd = userObj.getStringField("pwd"); } + md5digest d; { - - string pwd = userObj.getStringField("pwd"); digestBuilder << user << pwd; string done = digestBuilder.str(); diff --git a/db/security_key.cpp b/db/security_key.cpp new file mode 100644 index 00000000000..e76c45075c4 --- /dev/null +++ b/db/security_key.cpp @@ -0,0 +1,103 @@ +// security_key.cpp +/* + * Copyright (C) 2010 10gen Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/** + * This file contains inter-mongo instance security helpers. Due to the + * requirement that it be possible to compile this into mongos and mongod, it + * should not depend on much external stuff. + */ + +#include "pch.h" +#include "security_key.h" +#include "../client/dbclient.h" + +#include <sys/stat.h> + +namespace mongo { + + bool noauth = true; + AuthInfo internalSecurity; + + bool setUpSecurityKey(const string& filename) { + struct stat stats; + + // check obvious file errors + if (stat(filename.c_str(), &stats) == -1) { + log() << "error getting file " << filename << ": " << strerror(errno) << endl; + return false; + } + + // check permissions: must be X00, where X is >= 4 + if ((stats.st_mode & (S_IRWXG|S_IRWXO)) != 0) { + log() << "permissions on " << filename << " are to open" << endl; + return false; + } + + const unsigned long long fileLength = stats.st_size; + if (fileLength < 6 || fileLength > 1024) { + log() << " key file " << filename << " has length " << stats.st_size + << ", must be between 6 and 1024 chars" << endl; + return false; + } + + FILE* file = fopen( filename.c_str(), "rb" ); + if (!file) { + log() << "error opening file: " << filename << ": " << strerror(errno) << endl; + return false; + } + + string str = ""; + + // strip key file + unsigned long long read = 0; + while (read < fileLength) { + char buf; + int readLength = fread(&buf, 1, 1, file); + if (readLength < 1) { + log() << "error reading file " << filename << endl; + return false; + } + read++; + + // check for whitespace + if ((buf >= '\x09' && buf <= '\x0D') || buf == ' ') { + continue; + } + + // check valid base64 + if ((buf < 'A' || buf > 'Z') && (buf < 'a' || buf > 'z') && (buf < '0' || buf > '9') && buf != '+' && buf != '/') { + log() << "invalid char in key file " << filename << ": " << buf << endl; + return false; + } + + str += buf; + } + + if (str.size() < 6) { + log() << "security key must be at least 6 characters" << endl; + return false; + } + + log(1) << "security key: " << str << endl; + + // createPWDigest should really not be a member func + DBClientConnection conn; + internalSecurity.pwd = conn.createPasswordDigest(internalSecurity.user, str); + + return true; + } +} // namespace mongo diff --git a/db/security_key.h b/db/security_key.h new file mode 100644 index 00000000000..86f130777fa --- /dev/null +++ b/db/security_key.h @@ -0,0 +1,47 @@ +// security_key.h + +/** +* Copyright (C) 2009 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +namespace mongo { + + /** + * Internal secret key info. + */ + struct AuthInfo { + AuthInfo() { + user = "__system"; + } + string user; + string pwd; + }; + + // --noauth cmd line option + extern bool noauth; + extern AuthInfo internalSecurity; + + /** + * This method checks the validity of filename as a security key, hashes its + * contents, and stores it in the internalSecurity variable. Prints an + * error message to the logs if there's an error. + * @param filename the file containing the key + * @return if the key was successfully stored + */ + bool setUpSecurityKey(const string& filename); + +} // namespace mongo diff --git a/jstests/replsets/auth1.js b/jstests/replsets/auth1.js new file mode 100644 index 00000000000..519b799d867 --- /dev/null +++ b/jstests/replsets/auth1.js @@ -0,0 +1,181 @@ +// check replica set authentication + +load("jstests/replsets/rslib.js"); + +var name = "rs_auth1"; +var port = allocatePorts(4); +var path = "jstests/replsets/"; + + +print("reset permissions"); +run("chmod", "644", path+"key1"); +run("chmod", "644", path+"key2"); + + +print("try starting mongod"); +var m = runMongoProgram( "mongod", "--keyFile", path+"key1", "--port", port[0], "--dbpath", "/data/db/" + name); + + +print("should fail with wrong permissions"); +assert.eq(m, 2, "mongod should exit w/ 2: permissions too open"); +stopMongod(port[0]); + + +print("change permissions on #1 & #2"); +run("chmod", "600", path+"key1"); +run("chmod", "600", path+"key2"); + + +print("add a user to server0: foo"); +m = startMongodTest( port[0], name+"-0", 0 ); +m.getDB("admin").addUser("foo", "bar"); +m.getDB("test").addUser("bar", "baz"); +stopMongod(port[0]); + + +print("start up rs"); +var rs = new ReplSetTest({"name" : name, "nodes" : 3, "startPort" : port[0]}); +m = rs.restart(0, {"keyFile" : path+"key1"}); +var s = rs.start(1, {"keyFile" : path+"key1"}); +var s2 = rs.start(2, {"keyFile" : path+"key1"}); + +var result = m.getDB("admin").auth("foo", "bar"); +assert.eq(result, 1, "login failed"); +result = m.getDB("admin").runCommand({replSetInitiate : rs.getReplSetConfig()}); +assert.eq(result.ok, 1, "couldn't initiate: "+tojson(result)); + +var master = rs.getMaster().getDB("test"); +wait(function() { + var status = master.adminCommand({replSetGetStatus:1}); + return status.members && status.members[1].state == 2 && status.members[2].state == 2; + }); + +master.foo.insert({x:1}); +rs.awaitReplication(); + + +print("try some legal and illegal reads"); +var r = master.foo.findOne(); +assert.eq(r.x, 1); + +s.setSlaveOk(); +slave = s.getDB("test"); +var err = {}; +try { + r = slave.foo.findOne(); +} +catch(e) { + err = JSON.parse(e.substring(6)); +} +assert.eq(err.code, 10057); +err = {}; + +master.adminCommand({logout:1}); + +try { + r = master.foo.findOne(); +} +catch (e) { + err = JSON.parse(e.substring(6)); +} +assert.eq(err.code, 10057); +err = {}; + +result = slave.auth("bar", "baz"); +assert.eq(result, 1); + +r = slave.foo.findOne(); +assert.eq(r.x, 1); + + +print("add some data"); +master.auth("bar", "baz"); +for (var i=0; i<1000; i++) { + master.foo.insert({x:i, foo : "bar"}); +} +master.runCommand({getlasterror:1, w:3, wtimeout:60000}); + + +print("fail over"); +rs.stop(0); + +wait(function() { + function getMaster(s) { + var result = s.getDB("admin").runCommand({isMaster: 1}); + printjson(result); + if (result.ismaster) { + master = s.getDB("test"); + return true; + } + return false; + } + + if (getMaster(s) || getMaster(s2)) { + return true; + } + return false; + }); + + +print("add some more data 1"); +master.auth("bar", "baz"); +for (var i=0; i<1000; i++) { + master.foo.insert({x:i, foo : "bar"}); +} +master.runCommand({getlasterror:1, w:3, wtimeout:60000}); + + +print("resync"); +rs.restart(0); + + +print("add some more data 2"); +for (var i=0; i<1000; i++) { + master.foo.insert({x:i, foo : "bar"}); +} +master.runCommand({getlasterror:1, w:3, wtimeout:60000}); + + +print("add member with wrong key"); +var conn = new MongodRunner(port[3], "/data/db/"+name+"-3", null, null, ["--replSet","rs_auth1","--rest","--oplogSize","2", "--keyFile", path+"key2"], {no_bind : true}); +conn.start(); + + +master.getSisterDB("admin").auth("foo", "bar"); +var config = master.getSisterDB("local").system.replset.findOne(); +config.members.push({_id : 3, host : getHostName()+":"+port[3]}); +config.version++; +try { + master.adminCommand({replSetReconfig:config}); +} +catch (e) { + print("error: "+e); +} +reconnect(master); +master.getSisterDB("admin").auth("foo", "bar"); + + +print("shouldn't ever sync"); +for (var i = 0; i<30; i++) { + print("iteration: " +i); + var results = master.adminCommand({replSetGetStatus:1}); + printjson(results); + assert(results.members[3].state != 2); + sleep(1000); +} + + +print("stop member"); +stopMongod(port[3]); + + +print("start back up with correct key"); +conn = new MongodRunner(port[3], "/data/db/"+name+"-3", null, null, ["--replSet","rs_auth1","--rest","--oplogSize","2", "--keyFile", path+"key1"], {no_bind : true}); +conn.start(); + +wait(function() { + var results = master.adminCommand({replSetGetStatus:1}); + printjson(results); + return results.members[3].state == 2; + }); + diff --git a/jstests/replsets/key1 b/jstests/replsets/key1 new file mode 100644 index 00000000000..b5c19e4092f --- /dev/null +++ b/jstests/replsets/key1 @@ -0,0 +1 @@ +foop de doop diff --git a/jstests/replsets/key2 b/jstests/replsets/key2 new file mode 100644 index 00000000000..cbde8212841 --- /dev/null +++ b/jstests/replsets/key2 @@ -0,0 +1 @@ +other key |