/* Copyright 2015 MongoDB 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 .
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the GNU Affero General Public License in all respects
* for all of the code used other than as permitted herein. If you modify
* file(s) with this exception, you may extend this exception to your
* version of the file(s), but you are not obligated to do so. If you do not
* wish to do so, delete this exception statement from your version. If you
* delete this exception statement from all source files in the program,
* then also delete it in the license file.
*/
#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kAccessControl
#include "mongo/platform/basic.h"
#include "mongo/client/authenticate.h"
#include "mongo/base/status.h"
#include "mongo/base/status_with.h"
#include "mongo/bson/json.h"
#include "mongo/bson/util/bson_extract.h"
#include "mongo/client/sasl_client_authenticate.h"
#include "mongo/config.h"
#include "mongo/db/server_options.h"
#include "mongo/rpc/get_status_from_command_result.h"
#include "mongo/util/log.h"
#include "mongo/util/net/ssl_manager.h"
#include "mongo/util/net/ssl_options.h"
#include "mongo/util/password_digest.h"
namespace mongo {
namespace auth {
using executor::RemoteCommandRequest;
using executor::RemoteCommandResponse;
using AuthRequest = StatusWith;
const char* const kMechanismMongoCR = "MONGODB-CR";
const char* const kMechanismMongoX509 = "MONGODB-X509";
const char* const kMechanismSaslPlain = "PLAIN";
const char* const kMechanismGSSAPI = "GSSAPI";
const char* const kMechanismScramSha1 = "SCRAM-SHA-1";
namespace {
const char* const kUserSourceFieldName = "userSource";
const BSONObj kGetNonceCmd = BSON("getnonce" << 1);
bool isOk(const BSONObj& o) {
return getStatusFromCommandResult(o).isOK();
}
StatusWith extractDBField(const BSONObj& params) {
std::string db;
if (params.hasField(kUserSourceFieldName)) {
if (!bsonExtractStringField(params, kUserSourceFieldName, &db).isOK()) {
return {ErrorCodes::AuthenticationFailed, "userSource field must contain a string"};
}
} else {
if (!bsonExtractStringField(params, saslCommandUserDBFieldName, &db).isOK()) {
return {ErrorCodes::AuthenticationFailed, "db field must contain a string"};
}
}
return std::move(db);
}
//
// MONGODB-CR
//
AuthRequest createMongoCRGetNonceCmd(const BSONObj& params) {
auto db = extractDBField(params);
if (!db.isOK())
return std::move(db.getStatus());
auto request = RemoteCommandRequest();
request.cmdObj = kGetNonceCmd;
request.dbname = db.getValue();
return std::move(request);
}
AuthRequest createMongoCRAuthenticateCmd(const BSONObj& params, StringData nonce) {
std::string username;
auto response = bsonExtractStringField(params, saslCommandUserFieldName, &username);
if (!response.isOK())
return response;
std::string password;
response = bsonExtractStringField(params, saslCommandPasswordFieldName, &password);
if (!response.isOK())
return response;
bool shouldDigest;
response = bsonExtractBooleanFieldWithDefault(
params, saslCommandDigestPasswordFieldName, true, &shouldDigest);
if (!response.isOK())
return response;
std::string digested = password;
if (shouldDigest)
digested = createPasswordDigest(username, password);
auto db = extractDBField(params);
if (!db.isOK())
return std::move(db.getStatus());
auto request = RemoteCommandRequest();
request.dbname = db.getValue();
BSONObjBuilder b;
{
b << "authenticate" << 1 << "nonce" << nonce << "user" << username;
md5digest d;
{
md5_state_t st;
md5_init(&st);
md5_append(&st, reinterpret_cast(nonce.rawData()), nonce.size());
md5_append(&st, reinterpret_cast(username.c_str()), username.size());
md5_append(&st, reinterpret_cast(digested.c_str()), digested.size());
md5_finish(&st, d);
}
b << "key" << digestToString(d);
request.cmdObj = b.obj();
}
return std::move(request);
}
void authMongoCR(RunCommandHook runCommand, const BSONObj& params, AuthCompletionHandler handler) {
invariant(runCommand);
invariant(handler);
// Step 1: send getnonce command, receive nonce
auto nonceRequest = createMongoCRGetNonceCmd(params);
if (!nonceRequest.isOK())
return handler(std::move(nonceRequest.getStatus()));
runCommand(nonceRequest.getValue(), [runCommand, params, handler](AuthResponse response) {
if (!response.isOK())
return handler(std::move(response));
// Ensure response was valid
std::string nonce;
BSONObj nonceResponse = response.data;
auto valid = bsonExtractStringField(nonceResponse, "nonce", &nonce);
if (!valid.isOK())
return handler({ErrorCodes::AuthenticationFailed,
"Invalid nonce response: " + nonceResponse.toString()});
// Step 2: send authenticate command, receive response
auto authRequest = createMongoCRAuthenticateCmd(params, nonce);
if (!authRequest.isOK())
return handler(std::move(authRequest.getStatus()));
runCommand(authRequest.getValue(), handler);
});
}
//
// X-509
//
AuthRequest createX509AuthCmd(const BSONObj& params, StringData clientName) {
if (clientName.empty()) {
return {ErrorCodes::AuthenticationFailed,
"Please enable SSL on the client-side to use the MONGODB-X509 authentication "
"mechanism."};
}
auto db = extractDBField(params);
if (!db.isOK())
return std::move(db.getStatus());
auto request = RemoteCommandRequest();
request.dbname = db.getValue();
std::string username;
auto response = bsonExtractStringFieldWithDefault(
params, saslCommandUserFieldName, clientName.toString(), &username);
if (!response.isOK()) {
return response;
}
if (username != clientName.toString()) {
StringBuilder message;
message << "Username \"";
message << params[saslCommandUserFieldName].valuestr();
message << "\" does not match the provided client certificate user \"";
message << clientName.toString() << "\"";
return {ErrorCodes::AuthenticationFailed, message.str()};
}
request.cmdObj = BSON("authenticate" << 1 << "mechanism"
<< "MONGODB-X509"
<< "user"
<< username);
return std::move(request);
}
// Use the MONGODB-X509 protocol to authenticate as "username." The certificate details
// have already been communicated automatically as part of the connect call.
void authX509(RunCommandHook runCommand,
const BSONObj& params,
StringData clientName,
AuthCompletionHandler handler) {
invariant(runCommand);
invariant(handler);
// Just 1 step: send authenticate command, receive response
auto authRequest = createX509AuthCmd(params, clientName);
if (!authRequest.isOK())
return handler(std::move(authRequest.getStatus()));
runCommand(authRequest.getValue(), handler);
}
//
// General Auth
//
bool isFailedAuthOk(const AuthResponse& response) {
return (response.status == ErrorCodes::AuthenticationFailed &&
serverGlobalParams.transitionToAuth);
}
void auth(RunCommandHook runCommand,
const BSONObj& params,
const HostAndPort& hostname,
StringData clientName,
AuthCompletionHandler handler) {
std::string mechanism;
auto authCompletionHandler = [handler](AuthResponse response) {
if (isFailedAuthOk(response)) {
// If auth failed in transitionToAuth, just pretend it succeeded.
log() << "Failed to authenticate in transitionToAuth, falling back to no "
"authentication.";
// We need to mock a successful AuthResponse.
return handler(
AuthResponse(RemoteCommandResponse(BSON("ok" << 1), BSONObj(), Milliseconds(0))));
}
// otherwise, call handler
return handler(std::move(response));
};
auto response = bsonExtractStringField(params, saslCommandMechanismFieldName, &mechanism);
if (!response.isOK())
return handler(std::move(response));
if (params.hasField(saslCommandUserDBFieldName) && params.hasField(kUserSourceFieldName)) {
return handler({ErrorCodes::AuthenticationFailed,
"You cannot specify both 'db' and 'userSource'. Please use only 'db'."});
}
if (mechanism == kMechanismMongoCR)
return authMongoCR(runCommand, params, authCompletionHandler);
#ifdef MONGO_CONFIG_SSL
else if (mechanism == kMechanismMongoX509)
return authX509(runCommand, params, clientName, authCompletionHandler);
#endif
else if (saslClientAuthenticate != nullptr)
return saslClientAuthenticate(runCommand, hostname, params, authCompletionHandler);
return handler({ErrorCodes::AuthenticationFailed,
mechanism + " mechanism support not compiled into client library."});
};
void asyncAuth(RunCommandHook runCommand,
const BSONObj& params,
const HostAndPort& hostname,
StringData clientName,
AuthCompletionHandler handler) {
auth(runCommand, params, hostname, clientName, std::move(handler));
}
} // namespace
void authenticateClient(const BSONObj& params,
const HostAndPort& hostname,
StringData clientName,
RunCommandHook runCommand,
AuthCompletionHandler handler) {
if (handler) {
// Run asynchronously
return asyncAuth(std::move(runCommand), params, hostname, clientName, std::move(handler));
} else {
// Run synchronously through async framework
// NOTE: this assumes that runCommand executes synchronously.
asyncAuth(runCommand, params, hostname, clientName, [](AuthResponse response) {
// DBClient expects us to throw in case of an auth error.
uassertStatusOK(response.status);
auto serverResponse = response.data;
uassert(ErrorCodes::AuthenticationFailed,
serverResponse["errmsg"].str(),
isOk(serverResponse));
});
}
}
BSONObj buildAuthParams(StringData dbname,
StringData username,
StringData passwordText,
bool digestPassword) {
return BSON(saslCommandMechanismFieldName << "SCRAM-SHA-1" << saslCommandUserDBFieldName
<< dbname
<< saslCommandUserFieldName
<< username
<< saslCommandPasswordFieldName
<< passwordText
<< saslCommandDigestPasswordFieldName
<< digestPassword);
}
StringData getSaslCommandUserDBFieldName() {
return saslCommandUserDBFieldName;
}
StringData getSaslCommandUserFieldName() {
return saslCommandUserFieldName;
}
} // namespace auth
} // namespace mongo