Skip to content

Commit 764709d

Browse files
CXX-389 Add auth to copydb helper (MONGODB-CR and SCRAM-SHA-1)
1 parent ca13496 commit 764709d

File tree

3 files changed

+265
-41
lines changed

3 files changed

+265
-41
lines changed

src/mongo/client/dbclient.cpp

Lines changed: 178 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@
5555
#include <algorithm>
5656
#include <cstdlib>
5757

58+
#ifdef MONGO_SSL
59+
#include "mongo/client/native_sasl_client_session.h"
60+
#endif
61+
5862
namespace mongo {
5963

6064
using std::auto_ptr;
@@ -149,6 +153,10 @@ namespace mongo {
149153
const char kAuthServiceName[] = "SERVICE_NAME";
150154
const char kAuthServiceRealm[] = "SERVICE_REALM";
151155

156+
const char kAuthMechMongoCR[] = "MONGODB-CR";
157+
const char kAuthMechScramSha1[] = "SCRAM-SHA-1";
158+
const char kAuthMechDefault[] = "DEFAULT";
159+
152160
const char* const kSupportedAuthMechanismProperties[] = {
153161
kAuthServiceName,
154162
kAuthServiceRealm
@@ -180,6 +188,24 @@ namespace mongo {
180188
}
181189
return bob.obj();
182190
}
191+
192+
string authKeyCopyDBMongoCR(const string& username,
193+
const string& password,
194+
const string& nonce)
195+
{
196+
md5digest d;
197+
string passwordDigest = createPasswordDigest( username, password );
198+
{
199+
md5_state_t st;
200+
md5_init(&st);
201+
md5_append(&st, reinterpret_cast<const md5_byte_t *>(nonce.c_str()), nonce.size() );
202+
md5_append(&st, reinterpret_cast<const md5_byte_t *>(username.data()), username.length());
203+
md5_append(&st, reinterpret_cast<const md5_byte_t *>(passwordDigest.c_str()), passwordDigest.size() );
204+
md5_finish(&st, d);
205+
}
206+
return digestToString( d );
207+
}
208+
183209
} // namespace
184210

185211
BSONObj ConnectionString::_makeAuthObjFromOptions(int maxWireVersion) const {
@@ -205,9 +231,9 @@ namespace mongo {
205231
if (!elt.eoo()) {
206232
bob.appendAs(elt, saslCommandMechanismFieldName);
207233
} else if (maxWireVersion >= 3) {
208-
bob.append(saslCommandMechanismFieldName, "SCRAM-SHA-1");
234+
bob.append(saslCommandMechanismFieldName, kAuthMechScramSha1);
209235
} else {
210-
bob.append(saslCommandMechanismFieldName, "MONGODB-CR");
236+
bob.append(saslCommandMechanismFieldName, kAuthMechMongoCR);
211237
}
212238

213239
elt = _options.getField("authMechanismProperties");
@@ -705,7 +731,7 @@ namespace mongo {
705731
BSONObjBuilder cmdObj;
706732
cmdObj.appendElements(cmd);
707733
_runCommandHook(&cmdObj);
708-
734+
709735
info = findOne(ns, cmdObj.done(), 0 , options);
710736
}
711737
else {
@@ -848,7 +874,7 @@ namespace mongo {
848874
!(params.hasField(saslCommandUserDBFieldName)
849875
&& params.hasField(saslCommandUserSourceFieldName)));
850876

851-
if (mechanism == StringData("MONGODB-CR", StringData::LiteralTag())) {
877+
if (mechanism == kAuthMechMongoCR) {
852878
std::string db;
853879
if (params.hasField(saslCommandUserSourceFieldName)) {
854880
uassertStatusOK(bsonExtractStringField(params,
@@ -942,9 +968,9 @@ namespace mongo {
942968
string& errmsg,
943969
bool digestPassword) {
944970
try {
945-
const char* mech = "MONGODB-CR";
971+
const char* mech = kAuthMechMongoCR;
946972
if( _maxWireVersion >= 3 ) {
947-
mech = "SCRAM-SHA-1";
973+
mech = kAuthMechScramSha1;
948974
}
949975
_auth(BSON(saslCommandMechanismFieldName << mech <<
950976
saslCommandUserDBFieldName << dbname <<
@@ -1066,15 +1092,153 @@ namespace mongo {
10661092
return runCommand(db.c_str(), b.done(), *info);
10671093
}
10681094

1069-
bool DBClientWithCommands::copyDatabase(const string &fromdb, const string &todb, const string &fromhost, BSONObj *info) {
1095+
bool DBClientWithCommands::copyDatabase(const string& fromdb,
1096+
const string& todb,
1097+
const string& fromhost,
1098+
const string& mechanism,
1099+
const string& username,
1100+
const string& password,
1101+
BSONObj *info) {
10701102
BSONObj o;
10711103
if ( info == 0 ) info = &o;
1072-
BSONObjBuilder b;
1073-
b.append("copydb", 1);
1074-
b.append("fromhost", fromhost);
1075-
b.append("fromdb", fromdb);
1076-
b.append("todb", todb);
1077-
return runCommand("admin", b.done(), *info);
1104+
BSONObjBuilder copydbCmd;
1105+
copydbCmd.append("copydb", 1);
1106+
copydbCmd.append("fromhost", fromhost);
1107+
copydbCmd.append("fromdb", fromdb);
1108+
copydbCmd.append("todb", todb);
1109+
1110+
// If we don't have a username, or if we're copying locally,
1111+
// just run the command without authenticating
1112+
if ( username == "" || fromhost == "" ) {
1113+
return runCommand("admin", copydbCmd.done(), *info);
1114+
}
1115+
1116+
// Otherwise, take or guess the auth mechanism
1117+
string authMech;
1118+
if ( mechanism != kAuthMechDefault ) {
1119+
uassert( 0, "auth mechanism must be MONGODB-CR or SCRAM-SHA-1",
1120+
( mechanism == kAuthMechMongoCR ||
1121+
mechanism == kAuthMechScramSha1));
1122+
authMech = mechanism;
1123+
}
1124+
#ifdef MONGO_SSL
1125+
else if ( (static_cast<DBClientBase*>(this))->getMaxWireVersion() >= 3 ) {
1126+
authMech = kAuthMechScramSha1;
1127+
}
1128+
#endif
1129+
else {
1130+
authMech = kAuthMechMongoCR;
1131+
}
1132+
1133+
if ( authMech == kAuthMechMongoCR ) {
1134+
// run MONGODB-CR copydb
1135+
BSONObj nonceInfo;
1136+
BSONObjBuilder nonceCmd;
1137+
BSONElement e;
1138+
string nonce;
1139+
1140+
nonceCmd.append("copydbgetnonce", 1);
1141+
nonceCmd.append("fromhost", fromhost);
1142+
1143+
verify( runCommand("admin", nonceCmd.done(), nonceInfo) );
1144+
{
1145+
BSONElement e = nonceInfo.getField("nonce");
1146+
verify( e.type() == String );
1147+
nonce = e.valuestr();
1148+
}
1149+
1150+
copydbCmd.append("username", username);
1151+
copydbCmd.append("nonce", nonce);
1152+
copydbCmd.append("key", authKeyCopyDBMongoCR(username, password, nonce));
1153+
return runCommand("admin", copydbCmd.done(), *info);
1154+
}
1155+
else {
1156+
// run SCRAM-SHA-1 copydb, but only with SSL
1157+
#ifndef MONGO_SSL
1158+
uassert( 0, "SCRAM-SHA-1 authentication requires driver to be built with SSL", false );
1159+
#endif
1160+
#ifdef MONGO_SSL
1161+
string hashedPwd = createPasswordDigest(username, password);
1162+
1163+
// create and initialize our sasl session
1164+
boost::scoped_ptr<SaslClientSession> session(new NativeSaslClientSession());
1165+
session->setParameter(SaslClientSession::parameterMechanism, kAuthMechScramSha1);
1166+
session->setParameter(SaslClientSession::parameterUser, username);
1167+
session->setParameter(SaslClientSession::parameterPassword, hashedPwd);
1168+
session->initialize();
1169+
1170+
// set up commands to feed the sasl state machine
1171+
BSONObj saslFirstCommandPrefix = BSON("copydbsaslstart" << 1 <<
1172+
"fromhost" << fromhost <<
1173+
"fromdb" << fromdb <<
1174+
saslCommandMechanismFieldName << kAuthMechScramSha1);
1175+
1176+
BSONObj saslFollowupCommandPrefix = BSON("copydb" << 1 <<
1177+
"fromhost" << fromhost <<
1178+
"fromdb" << fromdb <<
1179+
"todb" << todb);
1180+
1181+
BSONObj saslCommandPrefix = saslFirstCommandPrefix;
1182+
BSONObj inputObj = BSON(saslCommandPayloadFieldName << "");
1183+
bool isServerDone = false;
1184+
1185+
// send copydbsaslstart, then continue to send copydb until we are done.
1186+
while (!session->isDone()) {
1187+
string payload;
1188+
BSONType type;
1189+
1190+
Status status = saslExtractPayload(inputObj, &payload, &type);
1191+
if (!status.isOK()) {
1192+
throw DBException( str::stream() << "sasl session failure: " << status.reason(), 0 );
1193+
}
1194+
1195+
string responsePayload;
1196+
status = session->step(payload, &responsePayload);
1197+
if (!status.isOK()) {
1198+
throw DBException( str::stream() << "sasl session failure: " << status.reason(), 0 );
1199+
}
1200+
1201+
// build command to send to server
1202+
BSONObjBuilder commandBuilder;
1203+
commandBuilder.appendElements(saslCommandPrefix);
1204+
commandBuilder.appendBinData(saslCommandPayloadFieldName,
1205+
int(responsePayload.size()),
1206+
BinDataGeneral,
1207+
responsePayload.c_str());
1208+
BSONElement conversationId = inputObj[saslCommandConversationIdFieldName];
1209+
if (!conversationId.eoo()) {
1210+
commandBuilder.append(conversationId);
1211+
}
1212+
1213+
BSONObj command = commandBuilder.obj();
1214+
bool ok = runCommand("admin", command, inputObj);
1215+
1216+
ErrorCodes::Error code =
1217+
ErrorCodes::fromInt(inputObj[saslCommandCodeFieldName].numberInt());
1218+
1219+
if (!ok || code != ErrorCodes::OK) {
1220+
if (code == ErrorCodes::OK)
1221+
code = ErrorCodes::UnknownError;
1222+
// attempt to give a sane error message
1223+
if ( inputObj.hasField("errmsg") ) {
1224+
BSONElement e = inputObj.getField("errmsg");
1225+
uassert( 0, "fromhost " + fromhost + " doesn't support SCRAM-SHA-1, use MONGODB-CR",
1226+
( e.type() == String &&
1227+
strstr( e.valuestr(), "no such cmd: saslStart")));
1228+
}
1229+
throw OperationException( inputObj );
1230+
}
1231+
1232+
isServerDone = inputObj[saslCommandDoneFieldName].trueValue();
1233+
saslCommandPrefix = saslFollowupCommandPrefix;
1234+
}
1235+
if (!isServerDone) {
1236+
invariant(false);
1237+
}
1238+
return true;
1239+
#endif /* MONGO_SSL */
1240+
}
1241+
invariant(false);
10781242
}
10791243

10801244
bool DBClientWithCommands::setDbProfilingLevel(const string &dbname, ProfilingLevel level, BSONObj *info ) {
@@ -1581,7 +1745,7 @@ namespace mongo {
15811745
int options) {
15821746
if (DBClientWithCommands::runCommand(dbname, cmd, info, options))
15831747
return true;
1584-
1748+
15851749
if ( clientSet && isNotMasterErrorString( info["errmsg"] ) ) {
15861750
clientSet->isntMaster();
15871751
}

src/mongo/client/dbclientinterface.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -897,7 +897,13 @@ namespace mongo {
897897
898898
returns true if successful
899899
*/
900-
bool copyDatabase(const std::string &fromdb, const std::string &todb, const std::string &fromhost = "", BSONObj *info = 0);
900+
bool copyDatabase(const std::string& fromdb,
901+
const std::string& todb,
902+
const std::string& fromhost = "",
903+
const std::string& mechanism = "DEFAULT",
904+
const std::string& username = "",
905+
const std::string& password = "",
906+
BSONObj *info = 0);
901907

902908
/** The Mongo database provides built-in performance profiling capabilities. Uset setDbProfilingLevel()
903909
to enable. Profiling information is then written to the system.profile collection, which one can

0 commit comments

Comments
 (0)