Allow secrets stored in encrypted files to be loaded from route handlers

This commit is contained in:
Martchus 2022-07-10 17:18:29 +02:00
parent 05176d70ef
commit df2b5ba9f6
6 changed files with 59 additions and 14 deletions

View File

@ -83,6 +83,10 @@ set(CONFIGURATION_PACKAGE_SUFFIX
find_package(c++utilities${CONFIGURATION_PACKAGE_SUFFIX} 5.11.0 REQUIRED)
use_cpp_utilities(VISIBILITY PUBLIC)
# find passwordfile
find_package(passwordfile${CONFIGURATION_PACKAGE_SUFFIX} 5.0.0 REQUIRED)
use_password_file(VISIBILITY PUBLIC)
# find boost libraries
option(BOOST_STATIC_LINKAGE "${STATIC_LINKAGE}" "link statically against Boost (instead of dynamically)")
set(Boost_USE_MULTITHREADED ON)

View File

@ -51,39 +51,41 @@ static constexpr char toLower(const char c)
return (c >= 'A' && c <= 'Z') ? (c + ('a' - 'A')) : c;
}
UserPermissions ServiceSetup::Authentication::authenticate(std::string_view authorizationHeader) const
UserAuth ServiceSetup::Authentication::authenticate(std::string_view authorizationHeader) const
{
// extract user name and password from base64 encoded header value
auto auth = UserAuth();
if (!CppUtilities::startsWith(authorizationHeader, "Basic ") && authorizationHeader.size() < 100) {
return UserPermissions::DefaultPermissions;
return auth;
}
std::pair<std::unique_ptr<std::uint8_t[]>, std::uint32_t> data;
try {
data = CppUtilities::decodeBase64(authorizationHeader.data() + 6, static_cast<std::uint32_t>(authorizationHeader.size() - 6));
} catch (const CppUtilities::ConversionException &) {
return UserPermissions::DefaultPermissions;
return auth;
}
const auto parts = CppUtilities::splitStringSimple<std::vector<std::string_view>>(
std::string_view(reinterpret_cast<const char *>(data.first.get()), data.second), ":", 2);
if (parts.size() != 2) {
return UserPermissions::DefaultPermissions;
return auth;
}
// find relevant user
const std::string_view userName = parts[0], password = parts[1];
if (userName.empty() || password.empty()) {
return UserPermissions::DefaultPermissions;
return auth;
}
if (userName == "try" && password == "again") {
return UserPermissions::TryAgain;
auth.permissions = UserPermissions::TryAgain;
return auth;
}
const auto user = users.find(std::string(userName));
if (user == users.cend()) {
return UserPermissions::DefaultPermissions;
return auth;
}
constexpr auto sha512HexSize = 128;
if (user->second.passwordSha512.size() != sha512HexSize) {
return UserPermissions::DefaultPermissions;
return auth;
}
// hash password
@ -98,12 +100,15 @@ UserPermissions ServiceSetup::Authentication::authenticate(std::string_view auth
for (unsigned char hashNumber : hash) {
const auto digits = CppUtilities::numberToString(hashNumber, 16);
if ((toLower(*(i++)) != toLower(digits.size() < 2 ? '0' : digits.front())) || (toLower(*(i++)) != toLower(digits.back()))) {
return UserPermissions::DefaultPermissions;
return auth;
}
}
// return the user's permissions
return user->second.permissions;
auth.permissions = user->second.permissions;
auth.name = userName;
auth.password = password;
return auth;
}
} // namespace LibRepoMgr

View File

@ -16,6 +16,12 @@ enum class UserPermissions : std::uint64_t {
DefaultPermissions = ReadBuildActionsDetails,
};
struct UserAuth {
std::string_view name;
std::string_view password;
UserPermissions permissions = UserPermissions::DefaultPermissions;
};
constexpr UserPermissions operator|(UserPermissions lhs, UserPermissions rhs)
{
return static_cast<UserPermissions>(

View File

@ -162,7 +162,7 @@ struct LIBREPOMGR_EXPORT ServiceSetup : public LibPkg::Lockable {
std::unordered_map<std::string, UserInfo> users;
void applyConfig(const std::string &userName, const std::multimap<std::string, std::string> &multimap);
UserPermissions authenticate(std::string_view authorizationHeader) const;
UserAuth authenticate(std::string_view authorizationHeader) const;
} auth;
struct LIBREPOMGR_EXPORT Locks {

View File

@ -74,21 +74,34 @@ void Session::received(boost::system::error_code ec, size_t bytesTransferred)
respond(Render::makeAuthRequired(request));
return;
}
const auto userPermissions = m_setup.auth.authenticate(std::string_view(authInfo->value().data(), authInfo->value().size()));
const auto userAuth = m_setup.auth.authenticate(std::string_view(authInfo->value().data(), authInfo->value().size()));
using PermissionFlags = std::underlying_type_t<UserPermissions>;
if (static_cast<PermissionFlags>(userPermissions) & static_cast<PermissionFlags>(UserPermissions::TryAgain)) {
if (static_cast<PermissionFlags>(userAuth.permissions) & static_cast<PermissionFlags>(UserPermissions::TryAgain)) {
// send the 401 response again if credentials are 'try again' to show the password prompt for the XMLHttpRequest again
// note: This is kind of a hack. Maybe there's a better solution to make XMLHttpRequest forget wrongly entered credentials
// and instead show the login prompt again?
respond(Render::makeAuthRequired(request));
return;
}
if ((static_cast<PermissionFlags>(requiredPermissions) & static_cast<PermissionFlags>(userPermissions))
if ((static_cast<PermissionFlags>(requiredPermissions) & static_cast<PermissionFlags>(userAuth.permissions))
!= static_cast<PermissionFlags>(requiredPermissions)) {
respond(Render::makeForbidden(request));
return;
}
// prepare file with secrets for user
if(!userAuth.name.empty() && !userAuth.password.empty()) {
try {
m_secrets.clear();
m_secrets.setPath(argsToString("secrets/"sv, userAuth.name));
m_secrets.setPassword(userAuth.password.data(), userAuth.password.size());
} catch (const std::ios_base::failure &e) {
cerr << Phrases::WarningMessage << "Failed to close password file \"" << m_secrets.path() << "\" (before preparing new one): " << e.what() << Phrases::End;
}
}
}
// invoke the route's handler
// note: The error handling is in vain if an exception in a deferred handler is thrown.
try {
route.handler(move(params),
std::bind(
@ -96,6 +109,14 @@ void Session::received(boost::system::error_code ec, size_t bytesTransferred)
} catch (const BadRequest &badRequest) {
respond(Render::makeBadRequest(request, badRequest.what()));
}
// discard password; secrets are expected to be read on the immediate call of the route
try {
m_secrets.clearPassword();
m_secrets.close();
} catch (const std::ios_base::failure &e) {
cerr << Phrases::WarningMessage << "Failed to close password file \"" << m_secrets.path() << "\": " << e.what() << Phrases::End;
}
return;
}

View File

@ -3,6 +3,8 @@
#include "./typedefs.h"
#include <passwordfile/io/passwordfile.h>
#include <boost/beast/core.hpp>
#include <boost/beast/http.hpp>
@ -31,6 +33,7 @@ public:
void received(boost::system::error_code ec, std::size_t bytesTransferred);
void responded(boost::system::error_code ec, std::size_t bytesTransferred, bool shouldClose);
static boost::beast::string_view determineMimeType(std::string_view path, boost::beast::string_view fallback = "text/plain");
Io::PasswordFile &secrets();
private:
boost::asio::ip::tcp::socket m_socket;
@ -39,6 +42,7 @@ private:
std::unique_ptr<RequestParser> m_parser;
ServiceSetup &m_setup;
std::shared_ptr<void> m_res;
Io::PasswordFile m_secrets;
};
inline Session::Session(boost::asio::ip::tcp::socket &&socket, ServiceSetup &setup)
@ -63,6 +67,11 @@ inline boost::asio::ip::tcp::socket &Session::socket()
return m_socket;
}
inline Io::PasswordFile &Session::secrets()
{
return m_secrets;
}
} // namespace WebAPI
} // namespace LibRepoMgr