0
0
mirror of https://github.com/mongodb/mongo.git synced 2024-11-30 17:10:48 +01:00

Client and common support for SASL authentication.

SERVER-7130, SERVER-7131, SERVER-7133
This commit is contained in:
Andy Schwerin 2012-10-29 13:12:36 -04:00
parent 444675bbd5
commit 924afa4629
7 changed files with 613 additions and 3 deletions

View File

@ -200,6 +200,8 @@ add_option( "gcov" , "compile with flags for gcov" , 0 , True )
add_option("smokedbprefix", "prefix to dbpath et al. for smoke tests", 1 , False )
add_option("smokeauth", "run smoke tests with --auth", 0 , False )
add_option("use-sasl-client", "Support SASL authentication in the client library", 0, False)
add_option( "use-system-tcmalloc", "use system version of tcmalloc library", 0, True )
add_option( "use-system-pcre", "use system version of pcre library", 0, True )
@ -819,6 +821,12 @@ def doConfigure(myenv):
if not conf.CheckLib( v8_lib_choices ):
Exit(1)
env['MONGO_BUILD_SASL_CLIENT'] = bool(has_option("use-sasl-client"))
if env['MONGO_BUILD_SASL_CLIENT'] and not conf.CheckLibWithHeader(
"gsasl", "gsasl.h", "C", "gsasl_check_version(GSASL_VERSION);", autoadd=False):
Exit(1)
# requires ports devel/libexecinfo to be installed
if freebsd or openbsd:
if not conf.CheckLib("execinfo"):

View File

@ -126,6 +126,13 @@ commonFiles = [ "pch.cpp",
"db/dbmessage.cpp"
]
commonSysLibdeps = []
if env['MONGO_BUILD_SASL_CLIENT']:
commonFiles.extend(['client/sasl_client_authenticate.cpp',
'util/gsasl_session.cpp'])
commonSysLibdeps.append('gsasl')
# handle processinfo*
processInfoFiles = [ "util/processinfo.cpp" ]
@ -161,7 +168,8 @@ env.StaticLibrary('mongocommon', commonFiles,
'fail_point',
'$BUILD_DIR/third_party/pcrecpp',
'$BUILD_DIR/third_party/murmurhash3/murmurhash3',
'$BUILD_DIR/third_party/shim_boost'],)
'$BUILD_DIR/third_party/shim_boost'],
SYSLIBDEPS=commonSysLibdeps)
env.StaticLibrary("coredb", [
"client/parallel.cpp",

View File

@ -16,7 +16,11 @@ error_code("UnsupportedFormat", 12)
error_code("Unauthorized", 13)
error_code("TypeMismatch", 14)
error_code("Overflow", 15)
error_code("IllegalOperation", 16)
error_code("EmptyArrayOperation", 17)
error_code("InvalidLength", 16)
error_code("ProtocolError", 17)
error_code("AuthenticationFailed", 18)
error_code("CannotReuseObject", 19)
error_code("IllegalOperation", 20)
error_code("EmptyArrayOperation", 21)
error_class("NetworkError", ["HostUnreachable", "HostNotFound"])

View File

@ -0,0 +1,229 @@
/* Copyright 2012 10gen Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "mongo/client/sasl_client_authenticate.h"
#include <string>
#include "mongo/base/string_data.h"
#include "mongo/bson/util/bson_extract.h"
#include "mongo/util/base64.h"
#include "mongo/util/gsasl_session.h"
#include "mongo/util/log.h"
#include "mongo/util/mongoutils/str.h"
#include "mongo/util/net/hostandport.h"
namespace mongo {
using namespace mongoutils;
const char* const saslStartCommandName = "saslStart";
const char* const saslContinueCommandName = "saslContinue";
const char* const saslCommandAutoAuthorizeFieldName = "autoAuthorize";
const char* const saslCommandCodeFieldName = "code";
const char* const saslCommandConversationIdFieldName = "conversationId";
const char* const saslCommandDoneFieldName = "done";
const char* const saslCommandErrmsgFieldName = "errmsg";
const char* const saslCommandMechanismFieldName = "mechanism";
const char* const saslCommandMechanismListFieldName = "supportedMechanisms";
const char* const saslCommandPasswordFieldName = "password";
const char* const saslCommandPayloadFieldName = "payload";
const char* const saslCommandPrincipalFieldName = "principal";
const char* const saslCommandServiceHostnameFieldName = "serviceHostname";
const char* const saslCommandServiceNameFieldName = "serviceName";
const char* const saslDefaultDBName = "admin";
const char* const saslDefaultServiceName = "mongodb";
const char* const saslClientLogFieldName = "clientLogLevel";
namespace {
// Default log level on the client for SASL log messages.
const int defaultSaslClientLogLevel = 4;
} // namespace
Status saslExtractPayload(const BSONObj& cmdObj, std::string* payload, BSONType* type) {
BSONElement payloadElement;
Status status = bsonExtractField(cmdObj, saslCommandPayloadFieldName, &payloadElement);
if (!status.isOK())
return status;
*type = payloadElement.type();
if (payloadElement.type() == BinData) {
const char* payloadData;
int payloadLen;
payloadData = payloadElement.binData(payloadLen);
if (payloadLen < 0)
return Status(ErrorCodes::InvalidLength, "Negative payload length");
*payload = std::string(payloadData, payloadData + payloadLen);
}
else if (payloadElement.type() == String) {
try {
*payload = base64::decode(payloadElement.str());
} catch (UserException& e) {
return Status(ErrorCodes::FailedToParse, e.what());
}
}
else {
return Status(ErrorCodes::TypeMismatch,
(str::stream() << "Wrong type for field; expected BinData or String for "
<< payloadElement));
}
return Status::OK();
}
namespace {
/**
* Configure "*session" as a client gsasl session for authenticating on the connection
* "*client", with the given "saslParameters". "gsasl" and "sessionHook" are passed through
* to GsaslSession::initializeClientSession, where they are documented.
*/
Status configureSession(Gsasl* gsasl,
DBClientWithCommands* client,
const BSONObj& saslParameters,
void* sessionHook,
GsaslSession* session) {
std::string mechanism;
Status status = bsonExtractStringField(saslParameters,
saslCommandMechanismFieldName,
&mechanism);
if (!status.isOK())
return status;
status = session->initializeClientSession(gsasl, mechanism, sessionHook);
if (!status.isOK())
return status;
std::string service;
status = bsonExtractStringFieldWithDefault(saslParameters,
saslCommandServiceNameFieldName,
saslDefaultServiceName,
&service);
if (!status.isOK())
return status;
session->setProperty(GSASL_SERVICE, service);
std::string hostname;
status = bsonExtractStringFieldWithDefault(saslParameters,
saslCommandServiceHostnameFieldName,
HostAndPort(client->getServerAddress()).host(),
&hostname);
if (!status.isOK())
return status;
session->setProperty(GSASL_HOSTNAME, hostname);
BSONElement element = saslParameters[saslCommandPrincipalFieldName];
if (element.type() == String) {
session->setProperty(GSASL_AUTHID, element.str());
}
else if (!element.eoo()) {
return Status(ErrorCodes::TypeMismatch,
str::stream() << "Expected string for " << element);
}
element = saslParameters[saslCommandPasswordFieldName];
if (element.type() == String) {
session->setProperty(GSASL_PASSWORD, element.str());
}
else if (!element.eoo()) {
return Status(ErrorCodes::TypeMismatch,
str::stream() << "Expected string for " << element);
}
return Status::OK();
}
int getSaslClientLogLevel(const BSONObj& saslParameters) {
int saslLogLevel = defaultSaslClientLogLevel;
BSONElement saslLogElement = saslParameters[saslClientLogFieldName];
if (saslLogElement.trueValue())
saslLogLevel = 1;
if (saslLogElement.isNumber())
saslLogLevel = saslLogElement.numberInt();
return saslLogLevel;
}
} // namespace
Status saslClientAuthenticate(Gsasl *gsasl,
DBClientWithCommands* client,
const BSONObj& saslParameters,
void* sessionHook) {
GsaslSession session;
int saslLogLevel = getSaslClientLogLevel(saslParameters);
Status status = configureSession(gsasl, client, saslParameters, sessionHook, &session);
if (!status.isOK())
return status;
BSONObj saslFirstCommandPrefix = BSON(
saslStartCommandName << 1 <<
saslCommandMechanismFieldName << session.getMechanism());
BSONObj saslFollowupCommandPrefix = BSON(saslContinueCommandName << 1);
BSONObj saslCommandPrefix = saslFirstCommandPrefix;
BSONObj inputObj = BSON(saslCommandPayloadFieldName << "");
bool isServerDone = false;
while (!session.isDone()) {
std::string payload;
BSONType type;
status = saslExtractPayload(inputObj, &payload, &type);
if (!status.isOK())
return status;
LOG(saslLogLevel) << "sasl client input: " << base64::encode(payload) << endl;
std::string responsePayload;
status = session.step(payload, &responsePayload);
if (!status.isOK())
return status;
LOG(saslLogLevel) << "sasl client output: " << base64::encode(responsePayload) << endl;
BSONObjBuilder commandBuilder;
commandBuilder.appendElements(saslCommandPrefix);
commandBuilder.appendBinData(saslCommandPayloadFieldName,
int(responsePayload.size()),
BinDataGeneral,
responsePayload.c_str());
BSONElement conversationId = inputObj[saslCommandConversationIdFieldName];
if (!conversationId.eoo())
commandBuilder.append(conversationId);
if (!client->runCommand(saslDefaultDBName, commandBuilder.obj(), inputObj)) {
return Status(ErrorCodes::UnknownError,
inputObj[saslCommandErrmsgFieldName].str());
}
int statusCodeInt = inputObj[saslCommandCodeFieldName].Int();
if (0 != statusCodeInt)
return Status(ErrorCodes::fromInt(statusCodeInt),
inputObj[saslCommandErrmsgFieldName].str());
isServerDone = inputObj[saslCommandDoneFieldName].trueValue();
saslCommandPrefix = saslFollowupCommandPrefix;
}
if (!isServerDone)
return Status(ErrorCodes::ProtocolError, "Client finished before server.");
return Status::OK();
}
} // namespace mongo

View File

@ -0,0 +1,129 @@
/* Copyright 2012 10gen Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "mongo/base/status.h"
#include "mongo/bson/bsontypes.h"
#include "mongo/client/dbclientinterface.h"
struct Gsasl;
namespace mongo {
class BSONObj;
/**
* Attempts to authenticate "client" using the SASL protocol.
*
* Requires an initialized instance of the "gsasl" library, as the first
* parameter.
*
* The "saslParameters" BSONObj should be initialized with zero or more of the
* fields below. Which fields are required depends on the mechanism. Consult the
* libgsasl documentation.
*
* "mechanism": The string name of the sasl mechanism to use. Mandatory.
* "autoAuthorize": Truthy values tell the server to automatically acquire privileges on
* all resources after successful authentication, which is the default. Falsey values
* instruct the server to await separate privilege-acquisition commands.
* "database": The database target of the auth command. Optional for GSSAPI/Kerberos.
* "principal": The string name of the principal to authenticate, GSASL_AUTHID.
* "password": The password data, GSASL_PASSWORD.
* "serviceName": The GSSAPI service name to use. Defaults to "mongodb".
* "serviceHostname": The GSSAPI hostname to use. Defaults to the name of the remote host.
*
* Other fields in saslParameters are silently ignored.
*
* "sessionHook" is a pointer to optional data, which may be used by the gsasl_callback
* previously set on "gsasl". The session hook is set on an underlying Gsasl_session using
* gsasl_session_hook_set, and may be accessed by callbacks using gsasl_session_hook_get.
* See the gsasl documentation.
*
* Returns an OK status on success, and ErrorCodes::AuthenticationFailed if authentication is
* rejected. Other failures, all of which are tantamount to authentication failure, may also be
* returned.
*/
Status saslClientAuthenticate(Gsasl *gsasl,
DBClientWithCommands* client,
const BSONObj& saslParameters,
void* sessionHook);
/**
* Extracts the payload field from "cmdObj", and store it into "*payload".
*
* Sets "*type" to the BSONType of the payload field in cmdObj.
*
* If the type of the payload field is String, the contents base64 decodes and
* stores into "*payload". If the type is BinData, the contents are stored directly
* into "*payload". In all other cases, returns
*/
Status saslExtractPayload(const BSONObj& cmdObj, std::string* payload, BSONType* type);
// Constants
/// String name of the saslStart command.
extern const char* const saslStartCommandName;
/// String name of the saslContinue command.
extern const char* const saslContinueCommandName;
/// Name of the saslStart parameter indicating that the server should automatically grant the
/// connection all privileges associated with the principal after successful authentication.
extern const char* const saslCommandAutoAuthorizeFieldName;
/// Name of the field contain the status code in responses from the server.
extern const char* const saslCommandCodeFieldName;
/// Name of the field containing the conversation identifier in server respones and saslContinue
/// commands.
extern const char* const saslCommandConversationIdFieldName;
/// Name of the field that indicates whether or not the server believes authentication has
/// completed successfully.
extern const char* const saslCommandDoneFieldName;
/// Field in which to store error messages associated with non-success return codes.
extern const char* const saslCommandErrmsgFieldName;
/// Name of parameter to saslStart command indiciating the client's desired sasl mechanism.
extern const char* const saslCommandMechanismFieldName;
/// In the event that saslStart supplies an unsupported mechanism, the server responds with a
/// field by this name, with a list of supported mechanisms.
extern const char* const saslCommandMechanismListFieldName;
/// Field containing password information for saslClientAuthenticate().
extern const char* const saslCommandPasswordFieldName;
/// Field containing sasl payloads passed to and from the server.
extern const char* const saslCommandPayloadFieldName;
/// Field containing the string identifier of the principal to authenticate in
/// saslClientAuthenticate().
extern const char* const saslCommandPrincipalFieldName;
/// Field overriding the FQDN of the hostname hosting the mongodb srevice in
/// saslClientAuthenticate().
extern const char* const saslCommandServiceHostnameFieldName;
/// Field overriding the name of the mongodb service saslClientAuthenticate().
extern const char* const saslCommandServiceNameFieldName;
/// Default database against which sasl authentication commands should run.
extern const char* const saslDefaultDBName;
/// Default sasl service name, "mongodb".
extern const char* const saslDefaultServiceName;
}

View File

@ -0,0 +1,94 @@
/* Copyright 2012 10gen Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "mongo/util/gsasl_session.h"
#include <cstdlib>
#include <gsasl.h>
#include "mongo/util/assert_util.h"
namespace mongo {
GsaslSession::GsaslSession() : _gsaslSession(NULL), _done(false) {}
GsaslSession::~GsaslSession() {
if (_gsaslSession)
gsasl_finish(_gsaslSession);
}
std::string GsaslSession::getMechanism() const {
return gsasl_mechanism_name(_gsaslSession);
}
void GsaslSession::setProperty(Gsasl_property property, const StringData& value) {
gsasl_property_set_raw(_gsaslSession, property, value.data(), value.size());
}
Status GsaslSession::initializeClientSession(Gsasl* gsasl,
const StringData& mechanism,
void* sessionHook) {
return _initializeSession(&gsasl_client_start, gsasl, mechanism, sessionHook);
}
Status GsaslSession::initializeServerSession(Gsasl* gsasl,
const StringData& mechanism,
void* sessionHook) {
return _initializeSession(&gsasl_server_start, gsasl, mechanism, sessionHook);
}
Status GsaslSession::_initializeSession(
GsaslSessionStartFn sessionStartFn,
Gsasl* gsasl, const StringData& mechanism, void* sessionHook) {
if (_done || _gsaslSession)
return Status(ErrorCodes::CannotReuseObject, "Cannot reuse GsaslSession.");
int rc = sessionStartFn(gsasl, mechanism.data(), &_gsaslSession);
switch (rc) {
case GSASL_OK:
gsasl_session_hook_set(_gsaslSession, sessionHook);
return Status::OK();
case GSASL_UNKNOWN_MECHANISM:
return Status(ErrorCodes::BadValue, gsasl_strerror(rc));
default:
return Status(ErrorCodes::ProtocolError, gsasl_strerror(rc));
}
}
Status GsaslSession::step(const StringData& inputData, std::string* outputData) {
char* output;
size_t outputSize;
int rc = gsasl_step(_gsaslSession,
inputData.data(), inputData.size(),
&output, &outputSize);
if (GSASL_OK == rc)
_done = true;
switch (rc) {
case GSASL_OK:
case GSASL_NEEDS_MORE:
*outputData = std::string(output, output + outputSize);
free(output);
return Status::OK();
case GSASL_AUTHENTICATION_ERROR:
return Status(ErrorCodes::AuthenticationFailed, gsasl_strerror(rc));
default:
return Status(ErrorCodes::ProtocolError, gsasl_strerror(rc));
}
}
} // namespace mongo

View File

@ -0,0 +1,138 @@
/* Copyright 2012 10gen Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <gsasl.h>
#include <string>
#include "mongo/base/disallow_copying.h"
#include "mongo/base/status.h"
#include "mongo/base/string_data.h"
namespace mongo {
/**
* C++ wrapper around Gsasl_session.
*/
class GsaslSession {
MONGO_DISALLOW_COPYING(GsaslSession);
public:
GsaslSession();
~GsaslSession();
/**
* Initializes "this" as a client sasl session.
*
* May only be called once on an instance of GsaslSession, and may not be called on an
* instance on which initializeServerSession has been called.
*
* "gsasl" is a pointer to a Gsasl library context that will exist for the rest of
* the lifetime of "this".
*
* "mechanism" is a SASL mechanism name.
*
* "sessionHook" is user-supplied data associated with this session. If is accessible in
* the gsasl callback set on "gsasl" using gsasl_session_hook_get(). May be NULL. Owned
* by caller, and must stay in scope as long as this object.
*
* Returns Status::OK() on success, some other status on errors.
*/
Status initializeClientSession(Gsasl* gsasl,
const StringData& mechanism,
void* sessionHook);
/**
* Initializes "this" as a server sasl session.
*
* May only be called once on an instance of GsaslSession, and may not be called on an
* instance on which initializeClientSession has been called.
*
* "gsasl" is a pointer to a Gsasl library context that will exist for the rest of
* the lifetime of "this".
*
* "mechanism" is a SASL mechanism name.
*
* "sessionHook" is user-supplied data associated with this session. If is accessible in
* the gsasl callback set on "gsasl" using gsasl_session_hook_get(). May be NULL. Owned
* by caller, and must stay in scope as long as this object.
*
* Returns Status::OK() on success, some other status on errors.
*/
Status initializeServerSession(Gsasl* gsasl,
const StringData& mechanism,
void* sessionHook);
/**
* Returns the string name of the SASL mechanism in use in this session.
*
* Not valid before initializeServerSession() or initializeClientSession().
*/
std::string getMechanism() const;
/**
* Sets a property on this session.
*
* Not valid before initializeServerSession() or initializeClientSession().
*/
void setProperty(Gsasl_property property, const StringData& value);
/**
* Performs one more step on this session.
*
* Receives "inputData" from the other side and produces "*outputData" to send.
*
* Both "inputData" and "*outputData" are logically strings of bytes, not characters.
*
* For the first step by the authentication initiator, "inputData" should have 0 length.
*
* Returns Status::OK() on success. In that case, isDone() can be queried to see if the
* session expects another call to step(). If isDone() is true, the authentication has
* completed successfully.
*
* Any return other than Status::OK() means that authentication has failed, but the specific
* code or reason message may provide insight as to why.
*/
Status step(const StringData& inputData, std::string* outputData);
/**
* Returns true if this session has completed successfully.
*
* That is, returns true if the session expects no more calls to step(), and all previous
* calls to step() and initializeClientSession()/initializeServerSession() have returned
* Status::OK().
*/
bool isDone() const { return _done; }
private:
// Signature of gsas session start functions.
typedef int (*GsaslSessionStartFn)(Gsasl*, const char*, Gsasl_session**);
/**
* Common helper code for initializing a session.
*
* Uses "sessionStartFn" to initialize the underlying Gsasl_session.
*/
Status _initializeSession(GsaslSessionStartFn sessionStartFn,
Gsasl* gsasl, const StringData& mechanism, void* sessionHook);
/// Underlying C-library gsasl session object.
Gsasl_session* _gsaslSession;
/// See isDone(), above.
bool _done;
};
} // namespace mongo