summaryrefslogtreecommitdiff
path: root/src/mongo/client
diff options
context:
space:
mode:
authorVarun Ravichandran <varun.ravichandran@mongodb.com>2022-09-09 20:18:31 +0000
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2023-02-03 02:12:59 +0000
commit02a33339a42236c3ed822d6c3b174725abe08a99 (patch)
tree70b68bbcbafd81271a067bdb9550fa6999caa892 /src/mongo/client
parent05bd358d519327f2ec47698d3f5efbc768f8f5b5 (diff)
downloadmongo-02a33339a42236c3ed822d6c3b174725abe08a99.tar.gz
SERVER-70956: Support MONGODB-OIDC SASL mechanism in shell with token
Diffstat (limited to 'src/mongo/client')
-rw-r--r--src/mongo/client/SConscript3
-rw-r--r--src/mongo/client/authenticate.h1
-rw-r--r--src/mongo/client/cyrus_sasl_client_session.cpp4
-rw-r--r--src/mongo/client/mongo_uri.cpp18
-rw-r--r--src/mongo/client/native_sasl_client_session.cpp18
-rw-r--r--src/mongo/client/sasl_client_authenticate_impl.cpp14
-rw-r--r--src/mongo/client/sasl_client_session.h1
-rw-r--r--src/mongo/client/sasl_oidc_client_conversation.cpp99
-rw-r--r--src/mongo/client/sasl_oidc_client_conversation.h73
-rw-r--r--src/mongo/client/sasl_oidc_client_params.h46
-rw-r--r--src/mongo/client/sasl_oidc_client_params.idl21
11 files changed, 285 insertions, 13 deletions
diff --git a/src/mongo/client/SConscript b/src/mongo/client/SConscript
index d6cecca2600..372518ece45 100644
--- a/src/mongo/client/SConscript
+++ b/src/mongo/client/SConscript
@@ -84,6 +84,8 @@ saslClientSource = [
'sasl_client_authenticate_impl.cpp',
'sasl_client_conversation.cpp',
'sasl_client_session.cpp',
+ 'sasl_oidc_client_conversation.cpp',
+ 'sasl_oidc_client_params.idl',
'sasl_plain_client_conversation.cpp',
'sasl_scram_client_conversation.cpp',
]
@@ -125,6 +127,7 @@ saslClientEnv.Library(
"sasl_aws_client" if get_option('ssl') == 'on' else '',
],
LIBDEPS_PRIVATE=[
+ '$BUILD_DIR/mongo/db/auth/oidc_protocol',
'$BUILD_DIR/mongo/db/commands/server_status_core',
'$BUILD_DIR/mongo/shell/program_runner',
'$BUILD_DIR/mongo/util/net/http_client',
diff --git a/src/mongo/client/authenticate.h b/src/mongo/client/authenticate.h
index 7b841b2ee84..fc4a8df8e78 100644
--- a/src/mongo/client/authenticate.h
+++ b/src/mongo/client/authenticate.h
@@ -69,6 +69,7 @@ constexpr auto kMechanismGSSAPI = "GSSAPI"_sd;
constexpr auto kMechanismScramSha1 = "SCRAM-SHA-1"_sd;
constexpr auto kMechanismScramSha256 = "SCRAM-SHA-256"_sd;
constexpr auto kMechanismMongoAWS = "MONGODB-AWS"_sd;
+constexpr auto kMechanismMongoOIDC = "MONGODB-OIDC"_sd;
constexpr auto kInternalAuthFallbackMechanism = kMechanismScramSha1;
constexpr auto kSaslSupportedMechanisms = "saslSupportedMechs"_sd;
diff --git a/src/mongo/client/cyrus_sasl_client_session.cpp b/src/mongo/client/cyrus_sasl_client_session.cpp
index b1a5fbf75cd..6d1434085d3 100644
--- a/src/mongo/client/cyrus_sasl_client_session.cpp
+++ b/src/mongo/client/cyrus_sasl_client_session.cpp
@@ -32,6 +32,7 @@
#include "mongo/client/cyrus_sasl_client_session.h"
#include "mongo/base/init.h"
+#include "mongo/client/authenticate.h"
#include "mongo/client/native_sasl_client_session.h"
#include "mongo/util/allocator.h"
#include "mongo/util/assert_util.h"
@@ -47,7 +48,8 @@ void saslSetError(sasl_conn_t* conn, const std::string& msg) {
}
SaslClientSession* createCyrusSaslClientSession(const std::string& mech) {
- if ((mech == "SCRAM-SHA-1") || (mech == "SCRAM-SHA-256") || mech == "MONGODB-AWS") {
+ if ((mech == auth::kMechanismScramSha1) || (mech == auth::kMechanismScramSha256) ||
+ (mech == auth::kMechanismMongoAWS) || (mech == auth::kMechanismMongoOIDC)) {
return new NativeSaslClientSession();
}
return new CyrusSaslClientSession();
diff --git a/src/mongo/client/mongo_uri.cpp b/src/mongo/client/mongo_uri.cpp
index 835bfe51b92..786b7ea3c70 100644
--- a/src/mongo/client/mongo_uri.cpp
+++ b/src/mongo/client/mongo_uri.cpp
@@ -593,9 +593,10 @@ constexpr auto kAuthMechanismPropertiesKey = "mechanism_properties"_sd;
constexpr auto kAuthServiceName = "SERVICE_NAME"_sd;
constexpr auto kAuthServiceRealm = "SERVICE_REALM"_sd;
constexpr auto kAuthAwsSessionToken = "AWS_SESSION_TOKEN"_sd;
+constexpr auto kAuthOIDCAccessToken = "OIDC_ACCESS_TOKEN"_sd;
-constexpr std::array<StringData, 3> kSupportedAuthMechanismProperties = {
- kAuthServiceName, kAuthServiceRealm, kAuthAwsSessionToken};
+constexpr std::array<StringData, 4> kSupportedAuthMechanismProperties = {
+ kAuthServiceName, kAuthServiceRealm, kAuthAwsSessionToken, kAuthOIDCAccessToken};
BSONObj parseAuthMechanismProperties(const std::string& propStr) {
BSONObjBuilder bob;
@@ -621,8 +622,10 @@ BSONObj parseAuthMechanismProperties(const std::string& propStr) {
boost::optional<BSONObj> MongoURI::makeAuthObjFromOptions(
int maxWireVersion, const std::vector<std::string>& saslMechsForAuth) const {
// Usually, a username is required to authenticate.
- // However X509 based authentication may, and typically does,
- // omit the username, inferring it from the client certificate instead.
+ // However certain authentication mechanisms may omit the username.
+ // This includes X509, which infers the username from the certificate;
+ // AWS-IAM, which infers it from the session token;
+ // and OIDC, which infers it from the access token.
bool usernameRequired = true;
BSONObjBuilder bob;
@@ -642,7 +645,8 @@ boost::optional<BSONObj> MongoURI::makeAuthObjFromOptions(
it = _options.find("authMechanism");
if (it != _options.end()) {
bob.append(saslCommandMechanismFieldName, it->second);
- if (it->second == auth::kMechanismMongoX509 || it->second == auth::kMechanismMongoAWS) {
+ if (it->second == auth::kMechanismMongoX509 || it->second == auth::kMechanismMongoAWS ||
+ it->second == auth::kMechanismMongoOIDC) {
usernameRequired = false;
}
} else if (!saslMechsForAuth.empty()) {
@@ -696,6 +700,10 @@ boost::optional<BSONObj> MongoURI::makeAuthObjFromOptions(
if (parsed.hasField(kAuthAwsSessionToken)) {
bob.append(saslCommandIamSessionToken, parsed[kAuthAwsSessionToken].String());
}
+
+ if (parsed.hasField(kAuthOIDCAccessToken)) {
+ bob.append(saslCommandOIDCAccessToken, parsed[kAuthOIDCAccessToken].String());
+ }
}
it = _options.find("gssapiServiceName");
diff --git a/src/mongo/client/native_sasl_client_session.cpp b/src/mongo/client/native_sasl_client_session.cpp
index 146c344e42e..172a950c3cc 100644
--- a/src/mongo/client/native_sasl_client_session.cpp
+++ b/src/mongo/client/native_sasl_client_session.cpp
@@ -34,6 +34,7 @@
#include "mongo/base/init.h"
#include "mongo/bson/bsonobjbuilder.h"
#include "mongo/client/sasl_client_conversation.h"
+#include "mongo/client/sasl_oidc_client_conversation.h"
#include "mongo/client/sasl_plain_client_conversation.h"
#include "mongo/client/sasl_scram_client_conversation.h"
#include "mongo/client/scram_client_cache.h"
@@ -106,19 +107,28 @@ Status NativeSaslClientSession::initialize() {
"Cannot reinitialize NativeSaslClientSession.");
std::string mechanism = getParameter(parameterMechanism).toString();
- if (mechanism == "PLAIN") {
+ if (mechanism == auth::kMechanismSaslPlain) {
_saslConversation.reset(new SaslPLAINClientConversation(this));
- } else if (mechanism == "SCRAM-SHA-1") {
+ } else if (mechanism == auth::kMechanismScramSha1) {
_saslConversation.reset(
new SaslSCRAMClientConversationImpl<SHA1Block>(this, scramsha1ClientCache));
- } else if (mechanism == "SCRAM-SHA-256") {
+ } else if (mechanism == auth::kMechanismScramSha256) {
_saslConversation.reset(
new SaslSCRAMClientConversationImpl<SHA256Block>(this, scramsha256ClientCache));
#ifdef MONGO_CONFIG_SSL
// AWS depends on kms-message which depends on ssl libraries
- } else if (mechanism == "MONGODB-AWS") {
+ } else if (mechanism == auth::kMechanismMongoAWS) {
_saslConversation.reset(new SaslAWSClientConversation(this));
#endif
+ } else if (mechanism == auth::kMechanismMongoOIDC) {
+ auto userName = hasParameter(SaslClientSession::parameterUser)
+ ? getParameter(SaslClientSession::parameterUser)
+ : ""_sd;
+ auto accessToken = hasParameter(SaslClientSession::parameterOIDCAccessToken)
+ ? getParameter(SaslClientSession::parameterOIDCAccessToken)
+ : ""_sd;
+
+ _saslConversation.reset(new SaslOIDCClientConversation(this, userName, accessToken));
} else {
return Status(ErrorCodes::BadValue,
str::stream() << "SASL mechanism " << mechanism << " is not supported");
diff --git a/src/mongo/client/sasl_client_authenticate_impl.cpp b/src/mongo/client/sasl_client_authenticate_impl.cpp
index 3faec846279..ffba7ba146d 100644
--- a/src/mongo/client/sasl_client_authenticate_impl.cpp
+++ b/src/mongo/client/sasl_client_authenticate_impl.cpp
@@ -147,11 +147,13 @@ Status saslConfigureSession(SaslClientSession* session,
status = bsonExtractStringField(saslParameters, saslCommandUserFieldName, &value);
if (status.isOK()) {
session->setParameter(SaslClientSession::parameterUser, value);
- } else if ((targetDatabase != "$external") || (mechanism != "MONGODB-AWS")) {
+ } else if ((targetDatabase != NamespaceString::kExternalDb) ||
+ ((mechanism != auth::kMechanismMongoAWS) &&
+ (mechanism != auth::kMechanismMongoOIDC))) {
return status;
}
- const bool digestPasswordDefault = (mechanism == "SCRAM-SHA-1");
+ const bool digestPasswordDefault = (mechanism == auth::kMechanismScramSha1);
bool digestPassword;
status = bsonExtractBooleanFieldWithDefault(
saslParameters, saslCommandDigestPasswordFieldName, digestPasswordDefault, &digestPassword);
@@ -161,7 +163,8 @@ Status saslConfigureSession(SaslClientSession* session,
status = extractPassword(saslParameters, digestPassword, &value);
if (status.isOK()) {
session->setParameter(SaslClientSession::parameterPassword, value);
- } else if (!(status == ErrorCodes::NoSuchKey && targetDatabase == "$external")) {
+ } else if (!(status == ErrorCodes::NoSuchKey &&
+ targetDatabase == NamespaceString::kExternalDb)) {
// $external users do not have passwords, hence NoSuchKey is expected
return status;
}
@@ -171,6 +174,11 @@ Status saslConfigureSession(SaslClientSession* session,
session->setParameter(SaslClientSession::parameterAWSSessionToken, value);
}
+ status = bsonExtractStringField(saslParameters, saslCommandOIDCAccessToken, &value);
+ if (status.isOK()) {
+ session->setParameter(SaslClientSession::parameterOIDCAccessToken, value);
+ }
+
return session->initialize();
}
diff --git a/src/mongo/client/sasl_client_session.h b/src/mongo/client/sasl_client_session.h
index c2d47049697..340b1a76440 100644
--- a/src/mongo/client/sasl_client_session.h
+++ b/src/mongo/client/sasl_client_session.h
@@ -70,6 +70,7 @@ public:
parameterUser,
parameterPassword,
parameterAWSSessionToken,
+ parameterOIDCAccessToken,
numParameters // Must be last
};
diff --git a/src/mongo/client/sasl_oidc_client_conversation.cpp b/src/mongo/client/sasl_oidc_client_conversation.cpp
new file mode 100644
index 00000000000..a46a0cd4986
--- /dev/null
+++ b/src/mongo/client/sasl_oidc_client_conversation.cpp
@@ -0,0 +1,99 @@
+/**
+ * Copyright (C) 2023-present MongoDB, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Server Side Public License, version 1,
+ * as published by MongoDB, Inc.
+ *
+ * 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
+ * Server Side Public License for more details.
+ *
+ * You should have received a copy of the Server Side Public License
+ * along with this program. If not, see
+ * <http://www.mongodb.com/licensing/server-side-public-license>.
+ *
+ * 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 Server Side 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.
+ */
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/client/sasl_oidc_client_conversation.h"
+
+#include "mongo/client/sasl_client_session.h"
+#include "mongo/client/sasl_oidc_client_params.h"
+#include "mongo/db/auth/oidc_protocol_gen.h"
+
+namespace mongo {
+
+OIDCClientGlobalParams oidcClientGlobalParams;
+
+StatusWith<bool> SaslOIDCClientConversation::step(StringData inputData, std::string* outputData) {
+ switch (++_step) {
+ case 1:
+ return _firstStep(outputData);
+ case 2:
+ return _secondStep(inputData, outputData);
+ default:
+ return StatusWith<bool>(ErrorCodes::AuthenticationFailed,
+ str::stream()
+ << "Invalid client OIDC authentication step: " << _step);
+ }
+}
+
+StatusWith<bool> SaslOIDCClientConversation::_firstStep(std::string* outputData) {
+ // If an access token was provided without a username, proceed to the second step and send it
+ // directly to the server.
+ if (_principalName.empty()) {
+ if (_accessToken.empty()) {
+ return Status(ErrorCodes::AuthenticationFailed,
+ "Either a username or an access token must be provided for the "
+ "MONGODB-OIDC mechanism");
+ }
+ try {
+ auto ret = _secondStep("", outputData);
+ ++_step;
+ return ret;
+ } catch (const DBException& ex) {
+ return ex.toStatus();
+ }
+ }
+
+ // If the username is provided, then request information needed to contact the identity provider
+ // from the server.
+ auth::OIDCMechanismClientStep1 firstClientRequest;
+ firstClientRequest.setPrincipalName(StringData(_principalName));
+ auto firstClientRequestBSON = firstClientRequest.toBSON();
+ *outputData = std::string(firstClientRequestBSON.objdata(), firstClientRequestBSON.objsize());
+ return false;
+}
+
+StatusWith<bool> SaslOIDCClientConversation::_secondStep(StringData input,
+ std::string* outputData) {
+ // If the client already has a non-empty access token, then token acquisition can be skipped.
+ if (_accessToken.empty()) {
+ // TODO SERVER-70958: Implement device authorization grant flow to acquire token.
+ uasserted(ErrorCodes::NotImplemented,
+ "TODO: SERVER-70958 Implement device authorization grant flow to acquire token");
+ }
+
+ auth::OIDCMechanismClientStep2 secondClientRequest;
+ secondClientRequest.setJWT(_accessToken);
+ auto bson = secondClientRequest.toBSON();
+ *outputData = std::string(bson.objdata(), bson.objsize());
+
+ return true;
+}
+
+} // namespace mongo
diff --git a/src/mongo/client/sasl_oidc_client_conversation.h b/src/mongo/client/sasl_oidc_client_conversation.h
new file mode 100644
index 00000000000..7308fea62f9
--- /dev/null
+++ b/src/mongo/client/sasl_oidc_client_conversation.h
@@ -0,0 +1,73 @@
+/**
+ * Copyright (C) 2023-present MongoDB, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Server Side Public License, version 1,
+ * as published by MongoDB, Inc.
+ *
+ * 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
+ * Server Side Public License for more details.
+ *
+ * You should have received a copy of the Server Side Public License
+ * along with this program. If not, see
+ * <http://www.mongodb.com/licensing/server-side-public-license>.
+ *
+ * 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 Server Side 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.
+ */
+
+#pragma once
+
+#include "mongo/client/sasl_client_conversation.h"
+
+namespace mongo {
+
+class SaslOIDCClientConversation : public SaslClientConversation {
+ SaslOIDCClientConversation(const SaslOIDCClientConversation&) = delete;
+ SaslOIDCClientConversation& operator=(const SaslOIDCClientConversation&) = delete;
+
+public:
+ SaslOIDCClientConversation(SaslClientSession* clientSession,
+ StringData principalName,
+ StringData accessToken)
+ : SaslClientConversation(clientSession),
+ _principalName(principalName.rawData()),
+ _accessToken(accessToken.rawData()) {}
+
+ StatusWith<bool> step(StringData inputData, std::string* outputData) override;
+
+private:
+ // Step of the conversation - can be 1, 2, or 3.
+ int _step{0};
+
+ // Name of the user that is trying to authenticate. It will only be non-empty
+ // if the client is trying to use MONGODB-OIDC via the device authorization grant flow.
+ std::string _principalName;
+
+ // Serialized access token to be sent to the server. It will only be non-empty if the client is
+ // trying to use MONGODB-OIDC with a token obtained out-of-band.
+ std::string _accessToken;
+
+ // Generate the opening client-side message. This simply includes the principal name.
+ StatusWith<bool> _firstStep(std::string* output);
+
+ // Parse the server's response to the client-side message, which should contain the identity
+ // provider's issuer endpoint, the clientID, and the clientSecret. Then, perform the
+ // device authorization grant flow to retrieve a device code, present a user code and
+ // verification uri to the user, and poll the token endpoint with the device code until the user
+ // authenticates and a token is provided.
+ StatusWith<bool> _secondStep(StringData input, std::string* outputData);
+};
+
+} // namespace mongo
diff --git a/src/mongo/client/sasl_oidc_client_params.h b/src/mongo/client/sasl_oidc_client_params.h
new file mode 100644
index 00000000000..af47b12db03
--- /dev/null
+++ b/src/mongo/client/sasl_oidc_client_params.h
@@ -0,0 +1,46 @@
+/**
+ * Copyright (C) 2023-present MongoDB, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Server Side Public License, version 1,
+ * as published by MongoDB, Inc.
+ *
+ * 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
+ * Server Side Public License for more details.
+ *
+ * You should have received a copy of the Server Side Public License
+ * along with this program. If not, see
+ * <http://www.mongodb.com/licensing/server-side-public-license>.
+ *
+ * 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 Server Side 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.
+ */
+
+#pragma once
+
+#include <string>
+
+namespace mongo {
+/**
+ * OIDC Client parameters
+ */
+struct OIDCClientGlobalParams {
+ /**
+ * Access Token.
+ */
+ std::string oidcAccessToken;
+};
+
+extern OIDCClientGlobalParams oidcClientGlobalParams;
+} // namespace mongo
diff --git a/src/mongo/client/sasl_oidc_client_params.idl b/src/mongo/client/sasl_oidc_client_params.idl
new file mode 100644
index 00000000000..af073911e2d
--- /dev/null
+++ b/src/mongo/client/sasl_oidc_client_params.idl
@@ -0,0 +1,21 @@
+# Copyright (C) 2023-present MongoDB, Inc.
+
+global:
+ cpp_namespace: "mongo"
+ configs:
+ section: 'OIDC Options'
+ source: [ cli ]
+ cpp_includes:
+ - mongo/client/sasl_oidc_client_params.h
+
+imports:
+ - "mongo/db/basic_types.idl"
+
+configs:
+ oidcAccessToken:
+ description: >-
+ If set, the shell will pass this token to the server for any user that tries
+ authenticating with the MONGODB-OIDC mechanism. This will bypass the device authorization
+ grant flow.
+ arg_vartype: String
+ cpp_varname: oidcClientGlobalParams.oidcAccessToken