summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKristina <kristina@10gen.com>2010-12-27 16:05:40 -0500
committerKristina <kristina@10gen.com>2010-12-27 16:07:33 -0500
commitfbf7b90a1eac4b93180ecc2cf02ae8daadc5d7b3 (patch)
treed921ef4c2b85babac895d16b045d8187a7d58311
parent9a8dda83d13938e981194a87a5e60bdf2d623f50 (diff)
downloadmongo-fbf7b90a1eac4b93180ecc2cf02ae8daadc5d7b3.tar.gz
rs auth
-rw-r--r--SConstruct2
-rw-r--r--db/cmdline.cpp14
-rw-r--r--db/repl.cpp45
-rw-r--r--db/repl/connections.h11
-rw-r--r--db/repl/rs.cpp3
-rw-r--r--db/repl/rs_sync.cpp3
-rw-r--r--db/security.cpp4
-rw-r--r--db/security.h4
-rw-r--r--db/security_commands.cpp37
-rw-r--r--db/security_key.cpp103
-rw-r--r--db/security_key.h47
-rw-r--r--jstests/replsets/auth1.js181
-rw-r--r--jstests/replsets/key11
-rw-r--r--jstests/replsets/key21
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