Redfish Session: Implement MFA "Token" property
This commit implements multi-factor authentication "Token" property to
create redfish sessions when multi-factor token authentication enabled.
Tested by:
Verified redfish session and login redfish commands with or without
TOTP token for MFA enabled/disabled users.
User authentication with MFA token:
POST https://${bmc}/redfish/v1/SessionService/Sessions -d '{"UserName"
:"root", "Password": "0penBmc","Token":"510760"}'
User authentication without MFA token:
POST https://${bmc}/login -d '{"username" : "newuser", "password"
:"0penBmc"}'
POST https://${bmc}/redfish/v1/SessionService/Sessions -d '{"UserName"
:"newuser", "Password": "0penBmc"}'
In case of invalid MFA token or password then authentication fails and
returns "ResourceAtUriUnauthorized" error message.
Change-Id: I639163dd3d49ff8ed886f72c99ad264317d59c34
Signed-off-by: Ravi Teja <raviteja28031990@gmail.com>
diff --git a/include/authentication.hpp b/include/authentication.hpp
index 1690550..1a05efd 100644
--- a/include/authentication.hpp
+++ b/include/authentication.hpp
@@ -54,7 +54,7 @@
BMCWEB_LOG_DEBUG("[AuthMiddleware] User IPAddress: {}",
clientIp.to_string());
- int pamrc = pamAuthenticateUser(user, pass);
+ int pamrc = pamAuthenticateUser(user, pass, std::nullopt);
bool isConfigureSelfOnly = pamrc == PAM_NEW_AUTHTOK_REQD;
if ((pamrc != PAM_SUCCESS) && !isConfigureSelfOnly)
{
diff --git a/include/login_routes.hpp b/include/login_routes.hpp
index 7664592..7675066 100644
--- a/include/login_routes.hpp
+++ b/include/login_routes.hpp
@@ -153,7 +153,7 @@
if (!username.empty() && !password.empty())
{
- int pamrc = pamAuthenticateUser(username, password);
+ int pamrc = pamAuthenticateUser(username, password, std::nullopt);
bool isConfigureSelfOnly = pamrc == PAM_NEW_AUTHTOK_REQD;
if ((pamrc != PAM_SUCCESS) && !isConfigureSelfOnly)
{
diff --git a/include/pam_authenticate.hpp b/include/pam_authenticate.hpp
index 22c8695..36d307e 100644
--- a/include/pam_authenticate.hpp
+++ b/include/pam_authenticate.hpp
@@ -7,6 +7,12 @@
#include <span>
#include <string_view>
+struct PasswordData
+{
+ std::string password;
+ std::optional<std::string> token;
+};
+
// function used to get user input
inline int pamFunctionConversation(int numMsg, const struct pam_message** msgs,
struct pam_response** resp, void* appdataPtr)
@@ -20,8 +26,9 @@
{
return PAM_CONV_ERR;
}
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ PasswordData* appPass = reinterpret_cast<PasswordData*>(appdataPtr);
auto msgCount = static_cast<size_t>(numMsg);
-
// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays)
auto responseArrPtr = std::make_unique<pam_response[]>(msgCount);
auto responses = std::span(responseArrPtr.get(), msgCount);
@@ -41,20 +48,34 @@
case PAM_PROMPT_ECHO_OFF:
{
// Assume PAM is only prompting for the password as hidden input
- // Allocate memory only when PAM_PROMPT_ECHO_OFF is encounterred
- char* appPass = static_cast<char*>(appdataPtr);
- size_t appPassSize = std::strlen(appPass);
-
+ // Allocate memory only when PAM_PROMPT_ECHO_OFF is encountered
+ size_t appPassSize = appPass->password.size();
if ((appPassSize + 1) > PAM_MAX_RESP_SIZE)
{
return PAM_CONV_ERR;
}
- // Create an array for pam to own
- // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays)
- auto passPtr = std::make_unique<char[]>(appPassSize + 1);
- std::strncpy(passPtr.get(), appPass, appPassSize + 1);
-
- responses[i].resp = passPtr.release();
+ std::string_view message(msg.msg);
+ constexpr std::string_view passwordPrompt = "Password: ";
+ // String used by Google authenticator to ask for one time code
+ constexpr std::string_view totpPrompt = "Verification code: ";
+ if (message.starts_with(passwordPrompt))
+ {
+ response.resp =
+ strdup(appPass->password.c_str()); // Password input
+ }
+ else if (message.starts_with(totpPrompt))
+ {
+ if (!appPass->token)
+ {
+ return PAM_CONV_ERR;
+ }
+ response.resp =
+ strdup(appPass->token->c_str()); // TOTP input
+ }
+ else
+ {
+ return PAM_CONV_ERR;
+ }
}
break;
case PAM_ERROR_MSG:
@@ -76,16 +97,15 @@
* @brief Attempt username/password authentication via PAM.
* @param username The provided username aka account name.
* @param password The provided password.
+ * @param token The provided MFA token.
* @returns PAM error code or PAM_SUCCESS for success. */
inline int pamAuthenticateUser(std::string_view username,
- std::string_view password)
+ std::string_view password,
+ std::optional<std::string> token)
{
std::string userStr(username);
- std::string passStr(password);
-
- char* passStrNoConst = passStr.data();
- const struct pam_conv localConversation = {pamFunctionConversation,
- passStrNoConst};
+ PasswordData data{std::string(password), std::move(token)};
+ const struct pam_conv localConversation = {pamFunctionConversation, &data};
pam_handle_t* localAuthHandle = nullptr; // this gets set by pam_start
int retval = pam_start("webserver", userStr.c_str(), &localConversation,
diff --git a/redfish-core/lib/redfish_sessions.hpp b/redfish-core/lib/redfish_sessions.hpp
index 225e872..d449b59 100644
--- a/redfish-core/lib/redfish_sessions.hpp
+++ b/redfish-core/lib/redfish_sessions.hpp
@@ -211,12 +211,13 @@
std::string username;
std::string password;
std::optional<std::string> clientId;
+ std::optional<std::string> token;
if (!json_util::readJsonPatch(req, asyncResp->res, "UserName", username,
- "Password", password, "Context", clientId))
+ "Password", password, "Token", token,
+ "Context", clientId))
{
return;
}
-
if (password.empty() || username.empty() ||
asyncResp->res.result() != boost::beast::http::status::ok)
{
@@ -233,7 +234,7 @@
return;
}
- int pamrc = pamAuthenticateUser(username, password);
+ int pamrc = pamAuthenticateUser(username, password, token);
bool isConfigureSelfOnly = pamrc == PAM_NEW_AUTHTOK_REQD;
if ((pamrc != PAM_SUCCESS) && !isConfigureSelfOnly)
{